[IMPL-RPT-040] CC-zip(1) 命令对齐账本(claude-code-main (1).zip)#
对照
/home/kodehu03/Downloads/claude-code-main (1).zip(129 个 CC 命令)与 khy(195 router 命令名)
的逐项 diff。每条均经 node 提取 + Explore 读 CC 源理解背后逻辑(非显示)后分类。
三类:EQUIVALENT(khy 已有同义逻辑,补/别名即可)/NEW-LOCAL(khy 无、可诚实本地实现)/
INFRA-NA(云/CI/命名管道 IPC/远端守护/订阅计费,本地 CLI 无法诚实实现,绝不造空壳)。审计法:
node直读(grep 经 rtk 污染不可信)。CC 命令名经src/commands/<name>提取,khy 经getRouterCommandNames()。
A. NEW-LOCAL —— 真缺口,逐个实现(接线五件套)#
| CC 命令 | 背后逻辑 | khy 现状 | 实现要点 |
|---|---|---|---|
debug-tool-call | 解析 transcript JSONL,按 id 配对 tool_use↔tool_result,截 200 字,显示最近 N(默认5) | 无 | 纯叶子解析 + 薄壳读 transcript。零模型零网络,最干净 |
recap | 「离开期间」一句话回顾;CC forked 单轮模型查询 | 有 sessionRecapService(consolidate 在用)但无命令 | 薄壳命令复用既有 recap 服务;无模型→确定性 summary 诚实降级 |
copy | 复制最近(或第N条)助手回复 / 其中代码块到剪贴板(OSC52 + tmp 文件兜底) | 无 | 叶子选回复/抽代码块 + 薄壳剪贴板写 |
rename | 重命名当前会话;无参时模型生成 kebab 名 | 无(有 session 管理无 rename) | 薄壳写 session 标题;无模型→提示需显式名 |
tag | 给会话打可搜索标签(同标签再打=确认移除) | 无 | 薄壳 session 元数据写 |
color | 设会话提示条颜色(per-session) | 有 theme/skin(全局)非 per-session | 低优先;可并入 skin |
break-cache | marker 文件强制下次/全部 API 调用 miss 前缀缓存 | 无 | 低优先;niche 调试 |
heapdump | V8 堆快照落盘 | 无 | 低优先;niche 调试 |
B. EQUIVALENT —— khy 已有同义逻辑,补 CC 名作别名即「菜单补齐」#
| CC 命令 | khy 等价 |
|---|---|
mode | persona / role(行为预设) |
security-review | security(scan/audit/permissions 子命令) |
force-snip | snip(手动裁剪近期消息) |
skill-learning | skill learn / skill learned / learn / evolve / train |
skill-search | skill search 子命令 |
skills | skill list |
local-vault | vault(list/set/get/show/rm 密钥库) |
local-memory | memory / knowledge / remember |
provider | gateway config / model(选 AI 服务商) / NL provider 配置 |
reload-plugins | plugin reload 子命令 |
insights | insights(已存在,完全等价) |
commit-push-pr | pr create(git/gh 编排) |
web-tools | NL provider 配置 + web_search / search |
poor | effort(low/medium/high token 档) |
tui | khy 默认全 TUI(KHY_FULL_TUI),无需独立开关 |
workflows | workflow(canonical 工作流 CLI) |
sandbox | sandbox-toggle |
C. INFRA-NA —— 结构性缺失,诚实不实现(不造空壳)#
| 类别 | CC 命令 |
|---|---|
| Anthropic 云 REST(订阅/计费/账号) | memory-stores skill-store artifacts privacy-settings extra-usage rate-limit-options passes feedback agents-platform schedule(triggers 云 cron) |
| 命名管道 / UDS / LAN 多进程 IPC | send attach detach peers pipes pipe-status |
| 云桥 / 远端守护 / 远端环境 | remote-control remote-control-server remote-env teleport web-setup bridge-kick |
| GitHub/Slack 云集成 | install-github-app install-slack-app subscribe-pr pr-comments(moved-to-plugin) |
| 浏览器扩展 + MCP + 订阅 | chrome |
| 内部 stub / 模板任务 | torch(reserved stub) ant-trace(disabled stub) job(TEMPLATES flag) |
| 2025 年度回顾插件novelty | think-back thinkback-play |
INFRA-NA 与前一对齐线程([[project_cc_alignment_missing_commands_tools]])同一诚实原则:云/CI/IPC/订阅
结构缺失绝不造空壳对话框;若 khy 未来具备对应底座(如 remotedev 已有),再行接线。
进度#
- [x] A1
debug-tool-call(最干净,优先)—— 叶子cli/debugToolCall.js(配对/截断/渲染,诚实标注「result not stored」)+ 薄壳handlers/debugToolCall.js(门控 KHY_DEBUG_TOOL_CALL,读当前 sessionId + buildConversationChain)+ 接线五件套(schema ROUTER_COMMANDS + slash 菜单 + router allowlist + switch case)。leaf 10/10 · 三守卫过 · bundled 三副本重建。 - [x] A2
recap(复用 sessionRecapService)—— 薄壳handlers/recap.js纯复用既有确定性 SSOTsessionRecapService.generateRecap+formatRecap(无模型也可用,诚实差异=对整段当前会话而非「away 边界」回顾)。门控 KHY_RECAP。空会话(sections:{})诚实降级只打 summary 不调 formatRecap(防抛)。handler 8/8 · 两守卫过 · bundled 三副本重建。 - [ ] A3
copy - [x] A3
copy—— 纯叶子cli/copyReply.js(parseCopyArgs/selectReply/extractCodeBlocks/buildCopyPayload,零 IO 确定性)+ 薄壳handlers/copy.js(门控 KHY_COPY,读 chain → contentBlockUtils.contentToText 压平 → imageService.writeClipboardText 真剪贴板)。用法 /copy · /copy N · /copy code [N]。诚实:无 OSC52 走系统工具,无内容/无块/写失败如实告知绝不假装。leaf 10/10 · 三守卫过 · bundled 三副本重建。 - [x] A4
rename(复用 renameSession)—— 薄壳handlers/rename.js纯复用既有 SSOTsessionPersistence.renameSession(原子写+200 上限+索引刷新)。门控 KHY_RENAME。诚实差异=无参不偷起模型生成名,提示需显式给名;快照缺失如实报错。handler 5/5 · 两守卫过 · bundled 三副本重建。 - [ ] A5
tag - [x] A5
tag—— 纯叶子cli/sessionTag.js(normalizeTag/parseTagArgs/applyTags toggle 切换+去重保序,零 IO)+ 薄壳handlers/tag.js(门控 KHY_TAG,读 loadSessionMeta.metadata.tags → 算完整新数组 → updateSessionMetadata 浅合并整组覆盖)。用法 /tag 列出 · /tag <名...> 打/去(同名再打=移除)。leaf 9/9 · 三守卫过 · bundled 三副本重建。 - [ ] A6(低优先·niche)
color(并入 skin per-session)·break-cache·heapdump - [x] A6 全部实现(非 NA,均诚实可用)——
- [x] heapdump —— 纯叶子
cli/heapDump.js(dumpId 生成、MemoryDiagnostics对象组装对齐 CC、措辞,确定性零 IO,now/sessionId/v8 统计量全注入)+ 薄壳handlers/heapdump.js(门控 KHY_HEAPDUMP,v8.writeHeapSnapshot落.heapsnapshot+fs.writeFileSync落诊断 JSON 到getDataDir('heapdump'))。背后逻辑=堆快照只含 V8 堆,诊断 JSON 才区分泄漏在 V8 堆还是原生内存(mallocedMemory/detachedContexts/RSS)。leaf 7/7 · 三守卫过 · e2e 真落盘实证 · bundled 三副本重建。 - [x] break-cache —— 非空壳:khy 确实用 Anthropic 前缀缓存(
claudeAdapter/multiFreeService对静态前缀打cache_control:ephemeral)。纯叶子cli/breakCache.js(scope 解析、nonce 注释构造、事件日志聚合对齐 CC readStats、措辞)+ 薄壳services/gateway/breakCacheState.js(marker/flag/jsonl 文件读写 +consumeCacheBreakNonce消费侧)+ 薄壳handlers/breakCache.js(once/always/off/--clear/status)。消费接线:claudeAdapter组装系统提示前调consumeCacheBreakNonce(process.env),有 marker/flag 则把 nonce 注释前置到system→ 改前缀哈希 → 下次/每次调用 miss 缓存;once 模式消费后删 marker(一次性)。门控 KHY_BREAK_CACHE 默认开 → 关时consumeCacheBreakNonce恒空(网关字节回退绝不注入)。leaf+state 9/9 · 三守卫过 · bundled 三副本重建。 - [x] color —— 纯叶子
cli/sessionColor.js(调色板=khy AGENT_COLORS 8 色、reset 别名、参数解析、resolveAccent优先级 bash>memory>会话色>null、措辞)+ 薄壳cli/sessionColorState.js(进程级活动色,首帧惰性从会话元数据播种)+ 薄壳handlers/color.js(门控 KHY_SESSION_COLOR,updateSessionMetadata(id,{color})持久 +setSessionColor即时)。消费接线:Ink TUIApp.js:1505的accent改走resolveAccent({bashMode,memoryMode,sessionColor,env})→ 喂给既有PromptFrame的accent(输入框边框 +❯标记色)。门控关 →resolveAccent忽略会话色 → accent 字节回退 null(=历史)。leaf+state 8/8 · TUI 304/304 无回归 · 三守卫过 · bundled 三副本重建。
- [x] heapdump —— 纯叶子
- [x] B 批量补 CC 名别名(menu 补齐)—— 14 条 CC 名别名,双机制:①
cli/aliases.jsALIAS_MAP(裸词分发,parseInput 在 switch 前解析,subCommand/defaultPositionals 全支持)②commandSchema.jsBUILTIN_SLASH_COMMANDS slash 条目带route(菜单可见 + /斜杠分发)。绝不另起 switch case —— 全部路由既有 canonical。映射:mode→persona · security-review→security scan · force-snip→snip · skills→skill list · skill-learning→skill learn · skill-search→skill search · local-vault→vault · local-memory→memory · provider→gateway config · reload-plugins→plugin reload · commit-push-pr→pr create · poor→effort low(defaultPositionals)· workflows→workflow · sandbox→sandbox-toggle。实证 parseInput:裸词 + /斜杠两形皆正确解析(/poor→effort args=['low']·/skills→skill list)。aliases+commandRegistry 回归 33/33 · model-hardcoding 过 · bundled 三副本(aliases.js+commandSchema.js)重建。刻意不纳入:tui(khy 默认全 TUI 无需开关)·web-tools(NL provider 配置 + web_search·非单命令可洁净别名)·insights(已存在完全等价)。 - [x] 工具面 diff(CC tools vs khy tools)—— 逐工具读 CC 源(packages/builtin-tools/src/tools/<Name>Tool/)对契约理解后分类:CC 60 个内置工具,khy 已有 63 个工具目录,52 EQUIVALENT · 1 MISSING-IMPLEMENTABLE(MonitorTool)· 7 INFRA-NA。
- EQUIVALENT 52:Bash↔shellCommand · FileRead/Write/Edit↔readFile/writeFile/editFile · Glob/Grep · NotebookEdit · WebSearch/WebFetch/WebBrowser · Agent · Goal · LocalMemoryRecall · Vault · CtxInspect · Skill/DiscoverSkills · Config · Sleep · Snip · Brief · TodoWrite · Task{Create,Get,List,Update,Stop,Output} · Team{Create,Delete} · ScheduleCron/CronList/CronDelete · ReviewArtifact · VerifyPlanExecution · EnterPlanMode/ExitPlanMode · EnterWorktree/ExitWorktree · SyntheticOutput=StructuredOutput · SearchExtraTools↔toolSearch · Execute(toolSearch select 架构吸收)· PowerShell · MCP/ListMcpResources/ReadMcpResource/McpAuth · AskUserQuestion · SendUserFile · PushNotification↔PushNotify(用户配置 ntfy/Bark/Discord/Slack/webhook 直连·比 CC bridge-only 更诚实)· ListPeers↔MeshPeer · SendMessage。
- INFRA-NA 7:ArtifactTool(云 artifact store·khy 已本地语义化写 getDataDir 不伪造 hosted URL)· RemoteTrigger(Anthropic 云 triggers API + OAuth)· SubscribePR/SuggestBackgroundPR(KAIROS 云 webhook/后台 PR agent·CC 自身即 stub)· TerminalCapture(需 PTY 复用器·CC+khy 皆空 stub)· OverflowTest/Tungsten(CC 内部 auto-gen stub·khy 无此目录=正确缺席)。
- MISSING-IMPLEMENTABLE 1 → DONE:见下。
- [x] MonitorTool 真后台化(唯一真缺口)—— 旧
execute()阻塞到proc.on('close')整段返回,却在 prompt 里自称"runs in the background"=契约自相矛盾(CC Monitor 核心价值=非阻塞 + 实时可读 outputFile + 退出通知,用于 tail -f / 轮询 / watch)。修:门控KHY_MONITOR_BACKGROUND默认开 → 非阻塞,stdout/stderr 流式写getDataDir('monitor')/<taskId>.log(用 Read 工具随时查看·实证读到 early 时进程仍 running),注册进backgroundShells(与 shellCommand run_in_background 同一 SSOT map)由 toolUseLoop 既有 drain 产<task_notification>,立即返回{taskId, backgroundTaskId, outputFile}。退出后collectBackgroundResults经summarize(entry.result.output)产一条一次性通知。门控关 → 逐字节回退旧阻塞行为(返回 exitCode+output 内联·无 taskId/outputFile)。CC 无 timeout(monitor 跑到自退)→ 后台模式仅当显式传 timeout 才 kill,否则不限时。isConcurrencySafe()随门控(开 true 关 false)。node:test 8/8(含非阻塞<1s 返回·实时可读·失败 exitCode·门控关回退·drain 一次性)· agent-rules/model-hardcoding 过 · bundled 三副本重建。 - [ ] 前端逐项对齐 + TUI 学习 —— 进行中(注重背后逻辑·逐刀)
- [x] 刀1「% until auto-compact」上下文警告状态机 —— 对齐 CC
autoCompact.ts::calculateTokenWarningState+TokenWarning.tsx的背后逻辑(非外观):① 何时显示=仅警告带内(used >= 阈值-20000,不从首 token nag)② 100% 分母=auto-compact 阈值(真触发点)非裸窗口 ③ 状态机=开则 dim「% until auto-compact」/关则着色「Context low (…) · Run /compact」。修两处真 bug:错误信号(sessionTokens.total累计无界 →contextWindow.used当前占用)+ 错误分母(裸 limit → 阈值)+ 恒显示(→ 警告带门控)。诚实:countdown 阈值参数化为 khy 真实触发比compactPipeline.AUTOCOMPACT_THRESHOLD(0.8,已导出)而非硬抄 CC 数字 → 0% 对齐真压缩事件。纯叶子cli/contextWarning.js(calculateTokenWarningState/buildContextWarning)收敛 repl 两渲染点(_renderPermissionBar着色 +_buildPermissionBarText文本)。门控 KHY_CONTEXT_WARNING 默认开·关→逐字节回退旧恒显示。leaf 11/11 · 三守卫过 · bundled 三副本重建 · GUARDS 红线节。 - [x] 刀2 spinner 计时/token byline 30s 揭示门控 —— 对齐 CC
Spinner/SpinnerAnimationRow.tsx的背后逻辑(非外观):SHOW_TOKENS_AFTER_MS=30000,wantsTimerAndTokens = verbose || hasRunningTeammates || effectiveElapsedMs > 30000→ 前 30s spinner 只显动词,30s 后才露Xm Ys · ↓ N tokensbyline(快回合永不闪计时器);effort/thinking 后缀只受宽度门控不受 30s 钟。修真 bug:spinner.js::_renderInnerparts = [elapsedStr]起手 + token 计数>0就推,全无 elapsed 门控 → 第一帧就闪计时+token。纯叶子cli/spinnerMeta.js(shouldShowTimerAndTokens/buildStatusParts)收敛spinner.jsbyline 组装,顺序恒 legacy[timer,↑in,↓out,effort],空括号已修(前 30s 无 effort → 只显动词无空())。门控 KHY_SPINNER_META_GATE 默认开·关→恒显示逐字节回退。leaf 9/9 · 三守卫过 · bundled 三副本重建 · GUARDS 红线节。 - [NA] 候选刀3 spinner 提示阈值(clear@30min/btw@30s) —— 诚实 NA:khy
spinner.js::_tipShown是无消费者死旗(设置后从不渲染任何 tip),对齐 CC tip 阈值属 net-new tip 渲染特性而非显示逻辑对齐 → 不造空壳(守 khy 诚实红线)。 - [x] 刀3 成本金额显示精度 SSOT(ccFormat.ccFormatCost) —— 对齐 CC
cost-tracker.ts::formatCost的背后逻辑(非外观):幅度自适应精度,一个0.5阈值切换位数——cost > 0.5 → round(cost,100).toFixed(2)(大额 2 位)/cost ≤ 0.5 → toFixed(4)(微额 4 位,防亚分成本¥0.005四舍五入塌 0)。CC 是formatTotalCost/formatModelUsage/BuiltinStatusLine唯一成本 SSOT;khy 此前三处各写一套:hudRenderer.js状态行0.01阈值、HUD 面板硬编码toFixed(4)/toFixed(2)、router.js/admin 硬编码toFixed(4)→ 大额会话误显¥123.4567过度精度。补齐ccFormat.js族最后缺位的成本格式化器(忠实移植 CCformatCost+round,不带货币符号返回纯数字串,¥/$由 call-site 拼,同族ccFormatTokens惯例)。收敛三 call-site(状态行 ×1 + HUD 面板 session/月 ×2 + /admin 总费用 ×1)。门控 KHY_CC_FORMAT(同族同门)默认开·关→返回 call-site 自带 legacy 逐字节回退各自旧 toFixed 口径。e2e:$17会话面板¥123.2500→¥123.25(关→¥123.2500)、微额$0.001→¥0.0073两态一致。leaf 10/10 · 既有 ccFormat 族回归 37/37 · 三守卫过 · bundled 三副本重建 · GUARDS 红线节。 - [x] 刀4 HUD token 计数走 ccFormatTokens SSOT —— 对齐 CC
format.ts::formatTokens(Intl 紧凑:12345→12.3k、1000→1k、1.5M→1.5m)。真缺口=hudRenderer.js用模块内私有本地fmtTokens偏离:>=10000→toFixed(0)k丢小数(12k)、>=1000→1.0k多余.0、无兆单位(1500k)——而 khy 其余所有 token 显示面(App/FooterBar/Spinner/CompactionProgress/useQueryBridge/turnStats)早已走ccFormatTokens,HUD 是唯一漏网。修:HUDfmtTokens在门控开时委派ccFormatTokens(''/非有限→落本地兜底),5 个 call-site 经此单点统一(状态行 session tokens + compactTokens + 面板 Context used/limit + agent tokens)。门控 KHY_CC_FORMAT(同族同门)默认开·关→逐字节回退本地旧三分支。e2e:面板 Context12.3k / 200k(关→12k / 200k)。test 3/3(门控开 12.3k·关回退 12k·SSOT vs 本地发散点 1000/123456/1500000)· 三守卫过 · bundled 三副本重建 · GUARDS 红线节。 - [x] 刀5 HUD elapsed 时长走 ccFormatDuration SSOT —— 对齐 CC
format.ts::formatDuration(与turnStats/spinner_formatElapsed同一真源)。真缺口=hudRenderer.js本地私有fmtDuration偏离且丢秒:>=60s→"${min}m"(90s显"1m"丢 30s)、小时态"${h}h${m}m"(无空格丢秒,1h1m1s→"1h1m")——而 khy 其余 elapsed 面早已带秒(turnStats走ccFormatDuration、spinner_formatElapsed显"Xm Ys"),HUD 是唯一丢秒漏网。修:HUDfmtDuration在门控开时委派ccFormatDuration(ms,{hideTrailingZeros:true})(''/非有限→落本地兜底),4 个 call-site(工具/压缩/会话/活动工具 elapsed)经此单点统一。hideTrailingZeros既忠实 CC(90s→"1m 30s"保秒、1h1m1s→"1h 1m 1s"带空格)又保 HUD 紧凑意图(60s→"1m"不显"1m 0s")。门控 KHY_CC_FORMAT(同族同门)默认开·关→逐字节回退本地旧三分支(丢秒口径)。e2e:面板 Session 行 90s"1m 30s"(关→"1m")。test 4/4(门控开 90s 保秒/60s 隐 0s+小时态带空格/门控关回退丢秒+无空格/SSOT vs 本地发散点直对照;经公开renderHudPanel验证因fmtDuration私有,读真sessionStart后 mockDate.now)· 三守卫过 · bundled 三副本重建 · GUARDS 红线节。 - [x] 刀6 剪贴板图片大小展示走 ccFormatFileSize SSOT —— 对齐 CC
format.ts::formatFileSize(续 health/storage/multimodal/aiUploadStore/archive 字节展示面统一收敛,见fileSizeSsot/fileSizeMediaSsot两测试)。真缺口=两个图片大小展示面仍各自发散且缺进位:repl.js5 个 call-site(「图片提示」剪贴板分析结果行)(sizeBytes/1024).toFixed(0)KB恒 KB(5MB图误显"5120KB"、1.5MB误显"1536KB"、800B塌"1KB")、imageService.printImagePreview2 分支≥1MB→"X.XMB" 否则 "XKB"(无bytes/GB档)。修=各加门控感知封装repl._formatImageSize/imageService._imageSizeStr(镜像既有atMentionInject._formatMentionSize/multimodalInputService._formatBytes模式),门控开委派ccFormatFileSize(''→落本地兜底),5+1 call-site 经此统一。门控 KHY_CC_FORMAT(同族同门)默认开·关→逐字节回退各 call-site 自己的历史口径(repl 恒toFixed(0)KB·imageService 2 分支带尾随.0)。e2e:5MB→"5MB"(关 repl→"5120KB"/imageService→"5.0MB")、1.5MB→"1.5MB"(关 repl→"1536KB")、800B→"800 bytes"(关→"1KB")。test 6/6(两 call-site 门控开 =ccFormatFileSize逐字节·CC 形态 MB/GB 进位+bytes 词+去.0·门控关各自旧口径·开关唯一分歧=SSOT 进位;经导出_formatImageSize/_imageSizeStr验证,镜像fileSizeMediaSsot导出私有 helper 惯例)·既有 file-size SSOT 回归 32/32(fileSizeMedia+fileSize+atMentionInject)·守卫过(agent-rules/model-hardcoding imageService 净·repl.js 1 warn=no-opaque-status:3175预存无关)·bundled 三副本重建(imageService+repl 各_imageSizeStr/_formatImageSize路由实证)·GUARDS 红线节。 - [x] 刀7
/cost·/usage报表 token 计数走 ccFormatTokens SSOT —— 对齐 CC/cost(cost-tracker.ts::formatModelUsage+Stats.tsx"Total tokens" 一律formatNumber紧凑记数1234567→"1.2m"),续刀4 token 收敛。真缺口=khy/cost·/usage报表子系统是唯一漏网:tokenUsageService.js三处局部fmtNum = n.toLocaleString('en-US')(formatInlineSummary/formatUsageReport/formatCostReport共 ~15 行 token 计数)+router.js两处内联.toLocaleString()(/cost today:2448、/cost history:2455)→ 全产全分隔符"1,234,567",既偏离 CC 报表逻辑,又与 khy 自身其余 6+ 个 token 面(HUD/Spinner/Footer/Compaction/turnStats,早走ccFormatTokens)不一致。修=新增tokenUsageService._fmtTokenCount(n, legacy, env)(镜像ccFormatCostOr(value,legacy,env)族签名,门控开委派ccFormatTokens、''→落 legacy),三处fmtNum改(n)=>_fmtTokenCount(n, n.toLocaleString('en-US'))(call-site 不动)、router 两处经tokenSvc._fmtTokenCount(x, x.toLocaleString())。门控 KHY_CC_FORMAT(同族同门)默认开·关→逐字节回退各 call-site 自带 legacy(旧toLocaleString全分隔符)。诚实精度权衡=报表/额度行紧凑后逐位精度由并列百分比承载(剩余 1.2m / 2m (62%)),这正是 CC/cost既定选择;需全精度者KHY_CC_FORMAT=off即逐字节取回旧1,234,567报表(门控即逃生舱,绝不丢旧行为)。e2e:formatCostReportON输入 1m / 合计 1.2m·OFF1,000,000 / 1,234,567·inline ON本次 1.2m · 剩余 0/100k·OFF1,234,567 · 0/100,000。test 6/6(门控开=ccFormatTokens逐字节·紧凑具体形态·门控关=toLocaleString全分隔符逐字节·开关唯一分歧·防呆非有限落 legacy·默认开)·既有 token/usage jest 回归 55/55(tokenUsageService+usageFormatter+usageTracker)·守卫过(agent-rules/model-hardcoding tokenUsageService+router 净)·bundled 三副本重建(各_fmtTokenCountON1.2m/OFF1,234,567+ router require 解析)。GUARDS 红线节。 - [x] 刀8 markdown blockquote 正文样式走 italic·正常亮度 SSOT(离开 ccFormat 族·新领域 markdown 渲染) —— 对齐 CC
src/utils/markdown.ts的case 'blockquote'背后逻辑(非外观):const bar = chalk.dim(BLOCKQUOTE_BAR); ... ${bar} ${chalk.italic(line)},且源码有逐字注释"Keep text italic but at normal brightness — chalk.dim is nearly invisible on dark themes."——CC 刻意把竖条 dim、正文 italic 且正常亮度,因深色主题下chalk.dim几乎不可见。真缺口=khymarkdownRenderer.js:601blockquote 正文c().dim(body)——竖条 dim(与 CC 一致)但正文也被 dim 掉=逐字复刻了 CC 这条注释专门要避免的可读性 bug(深色主题下 blockquote 正文几乎不可见)。该渲染分支无条件、经_renderMarkdownLiteInner→ 导出的renderMarkdownLite/renderMarkdownStreaming(助手输出主 markdown 渲染器)上屏=真用户可见。修=新纯叶子cli/blockquoteStyle.js(blockquoteBodyStyleEnabled(env)门控梯 +blockquoteBodyStyle(env)返回'italic'(门控开·CC)/'dim'(门控关·历史回退);零 IO 零业务 require,样式应用留 call-site=同orderedListAlign对齐算法留叶子、着色留 renderer 的分工),call-site 改c()[blockquoteBodyStyle()](body)。刻意不纳入:竖条始终 dim(与 CC 一致,本就对)·│竖条字形(CC▎)与 2 空格缩进=khy 主题选择非本刀。门控 KHY_BLOCKQUOTE_STYLE 默认开·关→逐字节回退c().dim(body)。e2e:> An important caveat.门控开[dim │] [\x1b[3m italic body](对齐 CC)·关[dim │] [\x1b[2m dim body](历史字节回退)。test 6/6(叶子门控梯·'italic'/'dim'决策·唯一分歧锁定·渲染真 ANSI\x1b[3m开/\x1b[2m关/默认开 italic)·markdown 渲染回归 33/33(markdownStreaming/CodeWrap/TableCellWrap/Tighten/orderedListAlignRender/markdownEmphasis)+ aiRenderer.formatting jest 40/40·三守卫过(leaf-contract blockquoteStyle·agent-rules/model-hardcoding blockquoteStyle+markdownRenderer 净)·bundled 三副本重建(各 leaf 解析 + ON italic/OFF dim 实证)。GUARDS 红线节。 - [x] 刀9 markdown 行内下划线强调
_italic_/__bold__走 CommonMark 词内守卫 SSOT(续 markdown 渲染域·新类别:行内强调定界符) —— 对齐 CCsrc/utils/markdown.ts:CC 用marked词法器按 CommonMark 解析行内强调(em→chalk.italic/strong→chalk.bold),同时识别*/**与_/__两套定界符并套 flanking 规则(_emphasis_斜体而my_var_name词内下划线字面)。真缺口=khymarkdownRenderer.js:582-586行内强调链只认星号(***/**/*),完全无下划线规则 →_italic_/__bold__(标准 CommonMark·模型常产出)连定界符原样上屏(_emphasized_字面显示·强调丢失)。经renderMarkdownLite(aiRenderer.js:523经典 REPL + Ink TUITranscript.js/StreamingBlock.js)真上屏。修=新纯叶子cli/underscoreEmphasis.js(underscoreEmphasisEnabled门控梯 +applyUnderscoreEmphasis(text,styler):三定界符___/__/_长在前,承重=词内守卫——每定界符外侧(?<!\w)/(?!\w)+ 内侧(?=\S)/(?<=\S),使_foo_→样式而some_function_name/_a_b_→字面;chalk 应用经styler注入留 call-site)。接线=星号链跑完后、行内代码占位符还原(\x00IC…\x00无下划线·code_with_under受保护)之前插入门控if(underscoreEmphasisEnabled())步,styler={italic:c().italic, bold:c().bold, boldItalic:c().bold.italic}。承重设计点=词内守卫正是 khy 当初省略下划线的原因(防snake_case误斜体),非猜测而是 CommonMark intraword 精确规则。诚实边界:行级正则在已注入 ANSI 的文本上跑,下划线强调紧邻其他行内样式(无空格)时外侧m(ANSI 结尾)会挡住边界守卫=罕见边角,正则渲染固有限制,如实接受不伪装 AST。门控 KHY_UNDERSCORE_EMPHASIS 默认开·关→跳过该步→下划线原样逐字节回退。e2e:_emphasized_→\x1b[3memphasized(CC)·__strong__→\x1b[1mstrong·some_function_name→字面·关→_emphasized_字面。test 13/13(门控梯·基本三态·行首尾边界·承重 snake_case/词内字面·内侧空白守卫·混合·防呆·渲染真 ANSI 开/snake 不斜体/关字节回退/默认开/星号不受影响)·markdown 渲染回归 39/39(streaming/codeWrap/tableCellWrap/tighten/orderedListAlignRender/emphasis/blockquote)+aiRenderer.formatting jest 40/40·三守卫过·bundled 三副本重建(各 leaf 解析+ON italic+snake 字面+OFF 字面实证)。GUARDS 红线节。 - [NA] 候选刀(有序列表按嵌套深度切标号 1./a./i.)诚实 honest-NA —— CC
markdown.tsgetListNumber/numberToLetter/numberToRoman:depth 0/1→数字、depth 2→字母a.b.c.、depth 3→罗马i.ii.iii.,且 CC 走 marked AST 真实深度由 token 树参数listDepth携带(非缩进推断)。khymarkdownRenderer.js:596有序列表是无状态行级正则^(\s*)(\d+)\.\s+,orderedListAlign.js仅按缩进串相等分组,mermaid._renderNestedListTrees只吃 bullet 不吃 ordered → khy 无任何结构/AST 深度信息。真分歧确凿(1./1./1.vs CC1./a./i.)但不可忠实实现:markdown 每级缩进宽度可变且上下文相关(1.下缩进 3 vs10.下缩进 4 同深度不同缩进·blockquote 内整体缩进的顶层列表会被误升级),任何缩进→深度启发式都会误渲染常见情形(给本是顶层但缩进的列表打a./i.),严格劣于现状朴素数字。忠实版需把整个渲染器移植到真 markdown AST=独立大型架构变更非一刀。守诚实红线绝不造模糊缩进推断,记 NA。 - [x] 刀10 diff add/remove 行背景色条「填充到终端整宽」走 SSOT(新领域:diff 渲染布局·非 markdown) —— 对齐 CC
src/components/StructuredDiff/Fallback.tsx::formatDiff背后逻辑:CC 对每一 diff 行算const padding = Math.max(0, safeWidth - contentWidth)再在backgroundColor的<Text>内追加{line}{' '.repeat(padding)}(word-diff 分支同理padding = Math.max(0, width - usedWidth)),使红/绿背景条一路铺到终端右缘=CC 标志性「实心整行色条」。真缺口=khy 所有 diff 路径只把背景铺到文本末尾,右侧参差(12 - return foo;后即默认背景,多行 diff 呈锯齿右缘)。修=新纯叶子cli/diffFullWidth.js(diffFullWidthEnabled(env)门控梯 +diffRowPadCount(used,total)纯算术镜像 CCMath.max(0,width-content)+防呆 +diffRowPadSpaces;零 IO、终端宽度/显示宽度测量/chalk 全留 call-site),接线经典 ANSI 路径diffRenderer.js:renderDiff(/diff 命令,经router.js:1360/repl.js:3870,4448console.log直出)+renderStructuredDiff(经典 REPL 工具 write-diff)两函数共 8 处 bgHex 行,门控开时把纯文本已用宽度(displayWidthCJK 感知·剥 ANSI)补足背景空格到process.stdout.columns;context(无背景)/---/+++头不补。关键诚实边界(为何不接 Ink TUI 默认路径)=khy TUI 用 vanillaink@6.8.0,其build/output.js:174渲染每行无条件styledCharsToString(...).trimEnd()裁掉带背景色的尾随空格→在 TUI 追加尾随空格是静默无效(色条仍参差);CC 用的是@anthropic/ink分叉(不做此 trim)才能整宽。故 TUI 整宽色条记 honest-NA(改/分叉 ink 属架构变更非一刀,绝不上屏一个被 trim 吃掉的死「修复」)——能力只接经典 ANSI 路径(console.log直出无 ink trim,尾随背景空格存活·已实证 reset[39m[49m落在补白空格之后=背景覆盖)。门控 KHY_DIFF_FULL_WIDTH 默认开·关→不补=逐字节回退参差色条。e2e(FORCE_COLOR=3·columns=80):renderStructuredDiffadd/del 行可见宽度补到 80(context 行 7 不补)·renderDiff+/- 行 80(+++头 5 不补)·cols=120→120(跟随终端)·关→add 行 7(legacy 参差)。test 11/11(门控梯·diffRowPadCount算术+防呆 NaN/Inf/小数 floor+used≥total→0·diffRowPadSpaces关空串开空格串·集成开整宽/context 不补·背景覆盖补白·关字节回退·默认开·renderDiff 头不补+跟随 columns)·回归 writeDiff(node:test)25/25(regex/- b//\+ B/+ CJK/emoji.includes不受补白影响)+ab_test_interactivity 50/50+editStatLine 11/11+TUI 304/304(ToolLines 未动)+ab_interactivity_v2 60/61(GAP-2_formatToolResult预存失败无关)·三守卫过(leaf-contract diffFullWidth·agent-rules/model-hardcoding diffFullWidth+diffRenderer 净)·bundled 三副本重建(各 leaf 解析+整宽渲染实证)。GUARDS 红线节。 - [x] 刀11 markdown 行内链接
[text](url)展示形态走 SSOT(回 markdown 渲染域·新类别:链接展示决策) —— 对齐 CCsrc/utils/markdown.tslink case 背后逻辑两条刻意规则:①// Prevent mailto links from being displayed as clickable links→const email = token.href.replace(/^mailto:/,''); return email(mailto 剥 scheme 只显裸邮箱纯文本);②if (plainLinkText && plainLinkText !== token.href) createHyperlink(text); else createHyperlink(href)(展示文本===URL/空 → URL 只显一次不重复)。真缺口=khymarkdownRenderer.js:588通用[text](url)→${link.underline(text)} ${dim((${url}))}:①[Email](mailto:foo@bar)渲成Email (mailto:foo@bar)泄mailto:scheme 且邮箱埋进 dim 括号;②[https://x.com](https://x.com)渲成https://x.com (https://x.com)自我重复。修=新纯叶子cli/markdownLink.js(markdownLinkDisplayEnabled(env)门控梯 +planLinkDisplay(text,url,env)返展示计划{kind:'plain'|'url-only'|'text-url',…};零 IO、零业务 require、确定性,chalk 全留 call-site),接线markdownRenderer.js的[text](url)replace:plain→裸邮箱纯文本(对齐 CC 不着色)、url-only→link.underline(url)只显一次、text-url→历史text (dim url)。诚实边界(刻意不纳入)=khy 用显式text (dim url)而非 CC 的 OSC-8 可点击超链接(createHyperlink),在任何终端都能看清真实目标、不依赖 OSC-8 能力,是有意渲染选择而非缺陷 → 不引入 OSC-8(行为变更非显示对齐);空展示文本[](url)在 khy 永不命中既有正则([^\]]+要求≥1 字符),叶子仍防御性并入 url-only。门控 KHY_MARKDOWN_LINK_DISPLAY 默认开·关→一律text-url逐字节回退(连 mailto 也回退到泄 scheme 旧行为)。test 11/11(门控梯·planLinkDisplay mailto 剥 scheme+大小写不敏感/text===url 去重/空白防御/普通 text-url/门控关一律回退/null·undefined 防呆·集成经renderMarkdownLitemailto 绝不泄 scheme+URL 只显一次+普通仍 text(url)+门控关回退)·回归 markdownTighten/CodeWrap/TableCellWrap/TableWrap/Emphasis 34/34+markdownStreaming 8/8(全树无既有测试断言链接渲染→默认开无破坏)·三守卫过(leaf-contract markdownLink·agent-rules/model-hardcoding markdownLink+markdownRenderer 净)·bundled 三副本重建(各 leaf 解析+mailto 无 scheme 实证)。GUARDS 红线节。 - [x] 刀12 子 agent / 工具追踪器统计行
Done (N tool uses · X tokens · Ys)三分段构造走 SSOT(新类别:agent 统计行措辞·非 markdown) —— 对齐 CCpackages/builtin-tools/src/tools/AgentTool/UI.tsx:359-363背后逻辑:const result = [ totalToolUseCount===1?'1 tool use':${n} tool uses, formatNumber(totalTokens)+' tokens', formatDuration(totalDurationMs) ];Done (${result.join(' · ')})`——三件套刻意走=== 1单数守卫 +formatNumber(注意是 formatNumber 而非 formatTokens,保留尾随.0)+formatDuration(带 h/d 进位)。真缺口=khy **5 处各自手写**这三段(agentTreeView.formatStats、toolDisplayagent 行 /renderAgentDone/ ToolUseTracker、agentRenderer.renderAgentSummary):**①** 恒复数${n} tool uses(单数误显1 tool uses);**②** token 手写/1000 .toFixed(1)k(≥1M 显1500.0k tokens而非 CC1.5m,2150因 toFixed 截断显2.1k而非 CC 四舍五入2.2k);**③** 时长手写 min/sec **无小时进位**(3735000ms显62m 15s而非 CC1h 2m 15s,树视图135.0s/2.1s而非2m 15s/2s)。三段对应的 CC 忠实移植ccFormatNumber/ccFormatDuration**早已**在cli/ccFormat.js落地并接到相邻面(turnStats/hudRenderer/Spinner),这些 stat 行只是**漏接**(faithful-port miss,非新逻辑)。修=新纯叶子cli/agentStatLine.js(三个*Or(value, legacy, env)函数,镜像 ccFormat 的ccFormatCostOr形状;门控开→CC 形态,门控关→原样返回 call-site 传入 legacy;纯叶子链agentTreeView→agentStatLine→ccFormat,零 IO、仅 require 同族纯叶子 ccFormat),复用KHY_CC_FORMAT门控(本就是 formatNumber/formatDuration 家族总开关)。接线四处 call-site(agentTreeView.formatStats内部消费、toolDisplay三处 agent 行/renderAgentDone/ToolUseTracker、agentRenderer.renderAgentSummary)各传自带 legacy 串(各 call-site 历史 k/秒口径不同→由 call-site 传 legacy 而非叶子写死,字节回退绝不串味)。**诚实边界**=各 call-site 前缀字形(·/└/Done)+chalk dim/green 着色留各 call-site 样式层不动(叶子只产纯文本三分段);计数源(toolCalls/tokens/elapsed 累加)是另一层不碰。test 13/13(单数守卫开1→1 tool use关1→1 tool uses/token1500000→1.5m·2150→2.2k·1000→1.0k·500→500 tokens无 k·门控关回退/时长3735000→1h 2m 15s·135000→2m 15s·2100→2s·门控关回退/默认开)·**回归**:agentTreeView(jest)34/34(更新 1 处 legacy 断言elapsed:2100数字态2.1s→2s=默认开 ccFormatDuration 整秒下取整新口径;字符串态1.8s透传不变、token 2100→2.1k不变)+agentStatLine 13/13+toolDisplay(CommentLabel/ShellJson)9/9+ab_test_interactivity 50/50+TUI 304/304+ab_interactivity_v2 GAP-2 预存失败无关·三守卫过(leaf-contract agentStatLine+agentTreeView·agent-rules/model-hardcoding 四文件净)·bundled 三副本重建(各 leaf 解析+1 tool use/1.5m tokens/1h 2m 15s` 实证)。GUARDS 红线节。 - [x] 刀13 运行中子 agent「活动占位」liveness 走 SSOT(同 agent 树域续刀:状态文本·非措辞数字) —— 对齐 CC
src/components/AgentProgressLine.tsx::getStatusText背后逻辑:if (!isResolved) return lastToolInfo || 'Initializing…'——任何未完成(状态 RUNNING)的 agent 一律给一条状态行:起了工具显工具行,未起工具/未吐 prose 也显Initializing…,父级绝不会看到「只有名字、下面空一片」的行误读成卡死。真缺口=khyagentTreeView.detailText在「running+currentTool」「running+currentText」两分支后直接return agent.detail || ''→刚 spawn 还没起首个工具的 running agent detail 为空 → 无 detail 子行 → 渲成裸├ name(经典 agentRenderer + ink AgentTree 两前端皆然,因共用 detailText SSOT)。修=detailText末尾补一条门控分支status===RUNNING && !agent.detail && agentInitStatusEnabled(env)→'Initializing…'(…=U+2026 与 CC 逐字节一致);只对 RUNNING(PENDING 保裸名+○ 空心点表意·terminal 显 outcome),currentTool/currentText/detail 永远优先。detailText(agent, env)新增可选 env 第二参(默认process.env,buildAgentTreeRows内部调用沿用默认不破)。诚实边界=不动 tree glyph(CC├─/└─双字带横杠 vs khy├/└单字=纯字形美学·logic 极薄·不在本刀)·不动着色(各前端样式层)·只补「running 无活动→占位」这一条有明确 logic 的状态行。门控 KHY_AGENT_INIT_STATUS 默认开·关→detailText 末尾逐字节回退历史''。test:agentTreeView(jest)35/35(新增 liveness 描述块:running+idle→Initializing…/tool·text·outcome 优先/PENDING 仍裸名/ERROR 显 Failed/门控关off·0回退'';更新原detailText({status:RUNNING})断言''→'Initializing…'=默认开新口径)·回归 buildAgentTreeRows 既有断言用.find/.filter('agent')不钉总行数→running-idle 多出的 detail 行无破坏·TUI 304/304·ab_test_interactivity 50/50·三守卫过(leaf-contract/agent-rules/model-hardcoding agentTreeView 净)·bundled 三副本重建(各 leaf 解析+Initializing…开/''关实证)。GUARDS 红线节。 - [x] 刀14 ink TUI live spinner 计时+token「30s reveal-gate」收口走既有 SSOT(同 spinner 域续刀:复用纯叶子非另起) —— 对齐 CC
src/components/Spinner/SpinnerAnimationRow.tsx背后逻辑:SHOW_TOKENS_AFTER_MS = 30_000,wantsTimerAndTokens = verbose || hasRunningTeammates || effectiveElapsedMs > 30000,showTimer/showTokens都要它 → 前 30s spinner 只显动词/glyph,30s 后才浮出计时+token meta(快回合永不闪计时器)。此 reveal 判据早有纯叶子 SSOTcli/spinnerMeta.js(shouldShowTimerAndTokens/isEnabled,门控 KHY_SPINNER_META_GATE),但只接了经典 REPL spinnercli/spinner.js——ink TUI 这条tui/ink-components/Spinner.js::buildSpinnerMeta此前从第 1 秒即显· 2s · ~40 tok(无阈值)=与 CC 及 khy 自己的经典 spinner 双双不一致的真缺口。修=inkbuildSpinnerMeta起手消费同一个叶子:shouldShowTimerAndTokens({ elapsedMs: sec*1000, gateEnabled: sm.isEnabled(env) })为 false → 返''(require 包 try → 叶子不可用跌穿到照常显示,绝不因加载失败静默吞 meta);与经典 spinner 共用同一门控 + 同一 30s 阈值常量,绝不在 ink 侧另写阈值常量或另起门控(早先草案曾内联SHOW_META_AFTER_MS+新门控KHY_SPINNER_META_THRESHOLD,自检发现重复既有 SSOT 后否决,收敛到复用)。诚实边界=khy 这条路径无 verbose/running-teammates 概念 → 仅 30s 钟生效(诚实只读叶子结论,绝不伪造旁路);ccFormat 数字/时长路由(刀早已落)与本 reveal-gate 正交,两门控独立。test:inkRenderSmoke(TUI harness)新增 G-C11(默认开 5/29/30s→''隐藏·31s→· 31s · ~340 tok浮出·90s→ccFormat1m 30s/1.2k·门控关0/off→第 1 秒即显·与 ccFormat 门控独立两关→即显+原始整数);G-C8 ccFormat-路由断言改 pinKHY_SPINNER_META_GATE:'off'隔离阈值(5s/12s 低秒态专测格式化口径);G-B 渲染帧测elapsedSec12→35(>30s 才让 meta 上屏);stall 测(elapsedSec:9)断言等待响应与 metaStr 正交不受影响。TUI 305/305(304 基线+G-C11 新测)·三守卫过(leaf-contract/agent-rules/model-hardcoding Spinner.js 净·新增 require 同族纯叶子 spinnerMeta)·bundled 三副本重建(各副本解析+≤30s 隐藏/>30s 浮出实证·spinnerMeta.js 因经典 spinner 早已 bundled 故 require 即解析)。GUARDS 红线节(并入既有 spinner reveal-gate 红线一/三,记 ink TUI 同消费)。 - [x] 刀15 ink TUI diff 内容列「裁切宽度」终端感知 + 展开换行走 SSOT(同 diff 域续刀:列宽算法·非行号位宽) —— 对齐 CC
src/components/StructuredDiff/Fallback.tsx::formatDiff背后逻辑:CC 按终端宽度算每行内容预算availableContentWidth = max(1, safeWidth - maxWidth - 1 - diffPrefixWidth)再wrapText(code, …, 'wrap')→ 长行换行铺开,绝不丢内容。真缺口=tui/ink-components/ToolLines.js::renderDiffRows对 add/del/ctx/other 四类行 +planWordDiffPairs全恒按固定 100 字clip(text,100)硬截:既无视终端宽度(80 列 TTY 上 100 字反而被 ink 二次尴尬换行),又renderDiffRows不收expanded→展开(Ctrl+O)只抬行上限(PREVIEW_ROWS/MAX_DIFF_ROWS)而不动横向→列 100 之后的代码展开后仍被静默吞掉,直接违背本文件自述(注释 109-115)「ctrl+o to expand actually reveals the full change」诚实原则;且与 khy 自己经典 ANSI 路cli/diffRenderer.js(无每行字数上限·console 自然换行)不一致。修=新纯叶子cli/diffContentWidth.js:diffClipWidth({columns,gutterWidth,expanded,env})纯算术(终端列宽由 ink call-site 注入·叶子不读 stdout)——门控关→恒LEGACY_CLIP(100)逐字节回退;门控开折叠→max(20, columns - gutter - 3 - 3)单行预算(80 列→72·200 列→190 比 legacy 更宽);门控开展开→Infinity(=不裁·clip(s,Infinity)===s·交 ink 像 CC 一样自动换行·内容完整)。接线=renderDiffRows(rows,h,Box,Text,expanded=false)新增expanded形参,内部经diffClipWidth算clipW后把四处clip(r.text,100)换clip(r.text,clipW);planWordDiffPairs(rows,wd,clipW=100)新增第三参(默认 100 保直接调用者/既有测试字节一致·renderDiffRows传实时预算或 Infinity);两 call-site(renderLiteralOutput的 shell-diff·ToolLines的 write-diff)均把已有的expanded透传。诚实边界(刻意不纳入)=展开态长词级配对行(del紧跟add且改动比低)由 ink 对多段着色<Text>自动换行铺开·本叶子只产宽度数值不重排彩色段;经典 ANSI 路本就无每行上限不在收敛之列;ink@6.8.0output.jstrimEnd 裁尾随背景空格的 NA 不受影响(内容仍完整)。门控KHY_DIFF_CONTENT_WIDTH默认开。test:diffContentWidth(node:test)8/8(门控梯/关恒 100/开展开→Infinity/开折叠→max(20,cols-gw-6)/窄 TTY 触底 20/列宽未知→80 兜底/无参防呆/默认开读 process.env);toolLinesWordDiff(jest)新增 clipW 用例(默认 100·Infinity 全长不截·40 收紧)→ 16/16;既有「segments 同 100 宽界」用例不传 clipW 走默认 100 字节不变。三守卫过(leaf-contract diffContentWidth.js 净·model-hardcoding/agent-rules ToolLines.js 净)·TUI 305/305 无回归·toolLinesWriteDiff+toolLinesWordDiff 36→(36 基线 + 1 新)·bundled 三副本重建(各副本 diffContentWidth.js 解析 + 关=100/开展开=Infinity/开折叠=72 实证·ToolLines.js require/clipW/expanded 透传字符串校验通过)。GUARDS 红线节(diff 内容列裁切宽度只此一处 diffContentWidth·折叠按列宽·展开 Infinity 换行·门控关恒 100 字节回退)。 - [x] 刀16 词级 diff「改动比例」度量口径从「非空白 token 计数」改「可见字符长度」走既有 SSOT(同 diff 域续刀:比例度量·非裁切宽度/行号位宽) —— 对齐 CC
src/components/StructuredDiff/Fallback.tsx::generateWordDiffElements背后逻辑:一行改得太多(changeRatio > CHANGE_THRESHOLD0.4)就放弃词级高亮、整行回退;CC 用可见字符长度度量这个比例——totalLength = removedLineText.length + addedLineText.length(含空白与未改的公共文本)、changedLength = Σ (added||removed) part.value.length、changeRatio = changedLength / totalLength。真缺口=khycli/wordDiff.js::computeWordDiff(diff 词级高亮的唯一 SSOT 纯叶子,被diffRenderer.js/ToolLines.js/ui/diffViewer.js三渲染器共用)历史却按非空白 token 计数度量(changedNonWS/totalNonWS,空白 token 既不进分子也不进分母),且自述注释本就声称「matching CC's CHANGE_THRESHOLD」=真·保真度 bug;两口径在阈值附近翻转判定:实证a = reallyLongOldName + b + c + d→改名 17 字标识符,字符口径 34/66≈0.515 > 0.4(CC 判整行回退),token 口径 2/18≈0.111 ≤ 0.4(khy 误判词级)。修=改既有叶子 SSOT 而非另起:computeWordDiff(oldTokens,newTokens,env)新增可选 env 形参,内部按wordDiffCharRatioEnabled(env)分支——门控开→字符口径(tokenizeLine对每行精确切分故Σ token.length === line.length,从 token 求和即逐字节复刻 CC 的line.length口径,含前导缩进与未改公共文本进分母);门控关→逐字节回退 legacy 非空白 token 计数。renderWordDiffLine/computeWordDiffSegments同步透传 env(缺省process.env),三消费者均不传 env → 默认开。诚实边界(刻意不纳入)=CHANGE_THRESHOLD(0.4)、tokenizeLine切分规则、LCS/段合并(_coalesceSegments)均不动——本刀只改比例的度量口径这一处;不引入字符级 LCS(khy token 级切分本就够细,CJK 逐字成 token);着色/前缀留各 call-site。门控KHY_WORD_DIFF_CHAR_RATIO默认开。test:wordDiffSegments(node:test)11/11(新增刀16 字符 vs token 口径边界翻转:门控开→整行回退·门控关→词级只标改名段;门控关短词多改仍词级=字节回退验证;原 test5「仅缩进改动」注释纠正为「字符口径空白计入但缩进占比 6/24=0.25 ≤ 0.4 仍词级」);toolLinesWordDiff(jest)16/16(全经 computeWordDiffSegments 默认开·既有断言用小改动/纯改名→两口径同判无破坏)。三守卫过(leaf-contract/agent-rules/model-hardcoding wordDiff.js 净)·TUI 306/306 无回归·bundled 三副本重建(各副本 wordDiff.js 解析 + 门控开长标识符改名→solid·门控关→word-level·wordDiffCharRatioEnabled 导出实证)。GUARDS 红线节(词级 diff changeRatio 只此一处字符长度口径·绝不退回 token 计数·门控关逐字节回退)。 - [x] 刀17 命令/三方应用 literal stdout 每行裁切宽度复用
diffClipWidthSSOT(同 diff 域续刀:补齐刀15 漏过的姊妹分支·非新逻辑) —— 对齐 CCsrc/utils/terminal.ts::renderTruncatedContent(src/components/shell/OutputLine.tsx消费)背后逻辑:命令 stdout 按终端宽度算每行预算wrapWidth = max(terminalWidth - PADDING_TO_PREVENT_OVERFLOW(10), 10)再wrapText('wrap')→ 长行换行铺开绝不丢内容。真缺口=tui/ink-components/ToolLines.js::renderLiteralOutput的非 diff 分支(行 ~435)对每行恒按固定 100 字truncate(ln, 100)硬截——与刀15 修掉的 diff 老缺口同病:① 无视终端宽度(80 列 TTY 上裁 100 仍宽于终端→ink 二次换行→撑破foldOutput的 maxLines 行预算·正是探查报告的「counting basis」病);② 展开(Ctrl+O)后仍裁 100→列 100 之后的 stdout 静默吞掉·违背本文件自述「Ctrl+O 真正显示全貌」诚实原则。刀15 当时只修了 diff 分支(renderDiffRows的clip),literal 分支因走truncate(而非clip)被漏过。修=复用既有 SSOTdiffContentWidth.diffClipWidth而非另起新叶子/新门控(SSOT 纪律·同刀14 复用 spinnerMeta):renderLiteralOutput非 diff 分支算litClipW = diffClipWidth({columns:(process.stdout.columns||undefined), gutterWidth:4, expanded, env})后把truncate(ln, 100)换truncate(ln, litClipW)。gutterWidth=4= 外层工具块 marginLeft(1)+res 块 marginLeft(1)+行首⎿字宽(2) → 折叠cols-10逐字复刻 CC 的wrapWidth=cols-PADDING(10);展开 →Infinity(truncate(ln,Infinity)===ln整行全貌交 ink 自然换行);门控关(同KHY_DIFF_CONTENT_WIDTH)→ 100 字节回退。诚实边界(刻意不纳入)=①truncate的空白折叠语义(replace(/\s+/g,' '))是另一轴不动(本刀只改宽度·空白保真属另一个可议的未来刀);② 行 617 是shouldRenderTransparentBody另一门控关的 legacy 12 行预览字节回退,不碰(碰了破坏那个门控的字节契约);③ 行 554 错误行(truncate(ln,120))是另一展示面不碰;④foldOutput的 head+tail 折叠设计(khy 刻意头尾窗口·与 CC head-only 不同但各自正确)绝不改成 CC 的 head-only(那会删掉 khy 正确的尾部展示=违诚实红线)——本刀只治每行横向裁切宽度,不动纵向折叠策略。门控KHY_DIFF_CONTENT_WIDTH(复用刀15)默认开。test:inkRenderSmoke(TUI harness)新增刀17 两例(命令 stdout 折叠态 80 列→裁 70 行尾标记被裁·展开态 clipW=Infinity 整行全貌含尾标记·门控关KHY_DIFF_CONTENT_WIDTH=0折叠恒裁 100→86 字行全留字节回退)→ TUI 308/308(306 基线+2)。三守卫过(leaf-contract/agent-rules/model-hardcoding ToolLines.js 净·复用已 require 的 diffContentWidth 叶子无新依赖)·bundled 三副本重建(各副本 ToolLines.js 解析+diffClipWidth({columns:80,gutterWidth:4})折叠=70/展开=Infinity/门控关=100 实证)。GUARDS 红线节(并入刀15 diffContentWidth 块:literal stdout 行同走 diffClipWidth·gutterWidth=4 复刻 CC cols-10·绝不再写固定 truncate(ln,100))。 - [x] 刀18 工具「错误详情」多行折叠走新纯叶子
cli/toolErrorFold.js(同 fold 域续刀:错误展示面·补刀17 自述漏过的「行 554 错误行」) —— 对齐 CCsrc/components/FallbackToolUseErrorMessage.tsx背后逻辑:错误折叠态error.split('\n').slice(0, MAX_RENDERED_LINES)(10)行 + 独立 dim 页脚「… +N lines (ctrl+o to see all)」(plusLines = countCharInString(error,'\n') + 1 - MAX_RENDERED_LINES),展开(verbose/Ctrl+O)显示全部——绝不静默丢尾。真缺口=tui/ink-components/ToolLines.js错误分支(行 ~561)历史String(reason).split('\n').slice(0, 3):既静默丢弃超 3 行(无标记·用户看不出栈/构建/多行 stderr 错误下面还有内容)又无视 expanded(Ctrl+O 永不显全)——与刀17 修掉的 literal stdout「截断后 Ctrl+O 仍是空头支票」同病(刀17 自述「行 554 错误行属另一展示面」即此处)。关键自查发现的隐藏前置 bug=errorText(result)经repl/displayFormatters.js::stripInternalControlText末尾.replace(/\s*\n\s*/g,' ')把所有换行折成空格 → 任何多行错误早已被塌成一行→slice(0,3)在生产中永不触发→单改折叠零可见变化(=违诚实红线「绝不 ship 不改变可见输出的修复」)。故修=两段式(同一门控KHY_TOOL_ERROR_FOLD默认开):①保留换行——stripInternalControlText(s, opts)新增可选opts.preserveNewlines(不传 → 逐字节回退旧的「换行折空格→单行」·repl.js:7641 单行摘要消费者完全不受影响·实测 grep 确认errorText唯一消费者=ToolLines:563),errorText(result, env)在门控开时传{preserveNewlines:true}(逐行折空格+丢空行[含剥掉[SYSTEM:…]留下的空行]·保留行间结构),让多行错误按行铺开;②折叠——新纯叶子toolErrorFold.planErrorFold(lines, expanded, env)产{shown, hidden}:门控开折叠态取前ERR_RENDERED_LINES(10)+ honesthidden计数·展开 → 全显;门控关 → 逐字节回退 legacy silentslice(0,3)(hidden:0·无视 expanded)。call-site 渲hidden>0时缀独立 dim 页脚「… +N 行 (ctrl+o 展开)」。诚实边界(刻意不纳入)=_displayHint(已干净单行·早 return 不经 strip)不动·truncate(ln,120)单行宽度裁切(另一轴·刀17 的 literal 走 diffClipWidth 是另一分支)不动·preserveNewlines绝不改stripInternalControlText默认行为(共享单行消费者字节契约)·CC 的 "Error: " 前缀属各 call-site 样式层(khy 用红色+headline 表意)不纳入。门控KHY_TOOL_ERROR_FOLD默认开。test:toolErrorFold(node:test)10/10(门控梯/ERR_RENDERED_LINES===10/折叠 ≤10 全显·==10 全显·13 行→头 10+hidden 3/展开 40 全显 hidden 0/门控关 silent slice(0,3) 无视 expanded·LEGACY_ERR_LINES===3/防呆非数组/发散仅在超 legacy cap 后);inkRenderSmoke(TUI harness)新增刀18 三例(13 行错误折叠→显 ERRLN-01/10·藏 ERRLN-13·页脚+3/ctrl+o·展开 Ctrl+O→ERRLN-13 现身无页脚·门控关KHY_TOOL_ERROR_FOLD=0→塌单行 ERRLN-13 内联无 ctrl+o 页脚字节回退)→ TUI 318/318。三守卫过(leaf-contract toolErrorFold.js+displayFormatters.js 净·agent-rules ToolLines.js 净·model-hardcoding 三文件净)·bundled 三副本重建(各副本 toolErrorFold.js 解析+planErrorFold导出·displayFormatterspreserveNewlines保 3 行实证)。GUARDS 红线节。 - [x] 刀19 任务清单面板「隐藏项摘要」从原始总数改 CC 式按状态分解走纯叶子
taskPanelLines.summarizeHiddenTaskLines(同 task-panel 域续刀:隐藏项语义·非高度封顶/图标映射) —— 对齐 CCsrc/components/TaskListV2.tsx::hiddenSummary背后逻辑:任务清单超量被截断时,CC 在隐藏(被截断)任务上count(hiddenTasks, t=>t.status==='in_progress'/'pending'/'completed')按状态分类,按 in progress → pending → completed 次序拼成… +N in progress, M pending, K completed(仅非零项出现)——让用户一眼看出「没显示的那些」里有几个在跑、几个待办、几个已完成。真缺口=tui/ink-components/TaskListPanel.js尾切提示历史只给原始总数⋯ 仅显示末尾 N 项(另有 M 项未显示),M 是个不透明计数,看不出隐藏的是进行中还是已完成(信息量远低于 CC)。真实结构来源:被liveRegionBudget.capTaskLines尾切丢弃的头部行每条行首恰是panelStatusIcon写入的状态图标(✓/→/○/✗),故分解来自图标逆映射ICON_STATUS(真实结构,非模糊启发式——完全契合「注重背后的逻辑」)。修=三处加性接线(门控KHY_TASK_HIDDEN_BREAKDOWN默认开):①liveRegionBudget.capTaskLines加性返回hiddenLines(被丢弃的头切片arr.slice(0, len-cap)·无截断/非数组路径恒[]·不破坏既有.lines/.hidden消费者,仅 test 两处 whole-objectdeepEqual同步加hiddenLines:[]);②App.js把_capped.hiddenLines经_taskProps透传(Ctrl+T 隐藏 spread 同补hiddenLines:[]);③ 新纯叶子函数summarizeHiddenTaskLines(hiddenLines, env)按 CC 次序(进行中→待办→已完成→错误)产「2 进行中, 1 待办, 1 已完成」,TaskListPanel在其非空时渲⋯ 仅显示末尾 N 项(另有 <分解>)否则回退原始计数。诚实边界(刻意不纳入)=① 任一隐藏行行首非已知图标(如 V1 TodoWrite 折叠行- [ ])→summarizeHiddenTaskLines返 ''→回退原始总数,绝不少计(宁可粗也不撒谎);② 门控关 / 空数组 / 非数组 → ''→ 逐字节回退原始计数标记;③ 标签用中文(Khy 中文作用域)但计数+次序逻辑逐项对齐 CC(非措辞照搬);④ CC 的strikethrough(completed)/bold(in_progress)/blocked-by 显示/优先级截断排序属别层(本刀只治隐藏项摘要的分组口径,不动行内样式与截断策略——khy 尾锚定截断与 CC 优先级截断各自正确)。门控KHY_TASK_HIDDEN_BREAKDOWN默认开。test:taskPanelLines+liveRegionBudget(node:test)32/32(新增刀19:多状态 CC 次序/仅非零项/不可识别图标→''/空/门控关→''/trimStart 容前导空白/taskHiddenBreakdownEnabled门控梯·capTaskLines 加hiddenLines头切片+空回退);taskListPanel(TUI harness)新增刀19 三例(hiddenLines 带图标→分解标记2 进行中, 1 待办, 1 已完成·不可识别行→回退原始总数·门控关→回退原始总数)→ TUI 320/320。三守卫过(leaf-contract taskPanelLines.js+liveRegionBudget.js 净·agent-rules/model-hardcoding 四文件净·App.js 1 warning 预存)·bundled 三副本重建(各副本summarizeHiddenTaskLines/taskHiddenBreakdownEnabled导出+capTaskLines含hiddenLines+门控开分解/关回退实证)。GUARDS 红线节。 - [x] 刀20 agent 树「时长 elapsed」控制器路径走时长 SSOT
agentStatLine.agentDurationLabelOr(同 agent 树域续刀:时长格式·两前端一致性·非统计行/liveness) —— 对齐 CCsrc/utils/format.ts::formatDuration背后逻辑:agent 树有两个前端,ink TUI 走事件溯源 number elapsed,经agentTreeView.formatStats的 number 分支 →agentDurationLabelOr→ 门控开ccFormatDuration("1m 30s"/"2s"),早已对齐 CC;但非 TTY/经典路径走cli/agentTreeController.js::toAgentArray(),历史把 elapsed 预格式化成恒(ms/1000).toFixed(1)+'s'(formatStats字符串分支原样 verbatim push),是两前端中唯一绕过时长 SSOT 的一支 → 同一个 agent 在 ink TUI 显 "1m 30s"、在非 TTY 控制器路径却显 "90.0s"(≥60s 不进位、亚秒 "2.1s" vs CC 整秒 "2s")。真缺口=控制器是不一致的孤儿(非「故意保留亚秒精度」——number 分支门控开本就已下取整到整秒,控制器只是没跟上)。修=SSOT 纪律,消费既有叶子而非另起:agentTreeControllerrequire('./agentStatLine'),toAgentArray的 elapsed 改elapsed > 0 ? agentDurationLabelOr(elapsed, \${(elapsed/1000).toFixed(1)}s\, process.env) : ''——保持 elapsed 仍是 STRING(agent-array 形状的全部消费者formatStats字符串分支 /toolDisplay.js:639/panels.js:661都按 string 原样 push·panelsdocstring 明示@param {string} elapsed),只把它的值改由 SSOT 产:门控开(KHY_CC_FORMAT默认开)→ CC 形态("1m 30s"/"2s"),门控关 → legacyX.Xs(90.0s/2.1s)逐字节回退。诚实边界(刻意不纳入)=①elapsed类型保持 string 不可改 number(否则 verbatim-push 消费者全断);②agentRenderer.js:140的Math.max(...map(a=>a.elapsed||0))对字符串本就 NaN——是预存的非 TTY summary 路径独立 bug(与 elapsed 是 "90.0s" 还是 "1m 30s" 无关,两者皆 NaN),本刀不引入也不修(非本刀职责,改它属另一刀);③ ink TUI 事件溯源 number 路径早已走 SSOT,本刀只补控制器这一支非 TTY/经典路径使两前端一致。门控KHY_CC_FORMAT(复用 ccFormat 家族总开关)默认开。test:agentTreeController(jest)7/7(门控开 ≥60s→"1m 30s"/亚秒→"2s"·门控关 byte-identical "90.0s"/"2.1s"·elapsed 0→''·与agentDurationLabelOr输出逐项一致[500/2100/59000/90000/3725000 两门控态]·error 态同走 SSOT·类型恒 string);agentTreeView(jest)35/35 + agentStatLine(node:test)13/13 无回归。三守卫过(leaf-contract/agent-rules/model-hardcoding agentTreeController.js 净)·TUI 320/320 无回归(ink 事件溯源 number 路径不受影响)·bundled 三副本重建(各副本agentTreeController.js解析+require('./agentStatLine')+门控开 90000→"1m 30s"/门控关→"90.0s" 实证)。GUARDS 红线节。 - [x] 刀21 spinner / step 行 / task 面板「秒级时长 elapsed」走时长 SSOT
renderTheme._formatElapsed → ccFormatDuration(时长格式器域续刀:补刀20 之后最后一支绕过 ccFormat 的孤儿) —— 对齐 CCsrc/utils/format.ts::formatDuration背后逻辑:renderTheme._formatElapsed(totalSec)是 spinner(spinner.js:140elapsedSec)、step 行(steps.js:68secs)、task 面板(taskMindMap.js:312/367/384/387(Date.now()-createdAt)/1000)三处秒级耗时展示的唯一格式器(经aiRenderer.js:29/panels.js:16/toolDisplay.js:20再导出)。历史实现本地手算:<60s显${sec}s,否则${min}m(整分丢秒显 "2m" 而非 "2m 0s")、且永不进位小时(分钟无界增长 "123m 4s" 而非 "2h 3m 4s")——是时长格式器家族(HUD{hideTrailingZeros}/ turnStatshumanizeElapsed/ CompactionProgressformatElapsed/ AgentTool_fmtElapsed/ agent-tree 刀20agentDurationLabelOr)中最后一支绕过ccFormatSSOT 的孤儿。真缺口=同一耗时在 HUD/agent 树显 CC 形态、在 spinner/task 面板却显本地异口径(≥1m 丢秒、≥1h 不进位)。修=SSOT 纪律,消费既有叶子而非另起:renderThemerequire('./ccFormat')取ccFormatEnabled/ccFormatDuration,_formatElapsed门控开 →ccFormatDuration(Math.max(0,Math.floor(totalSec))*1000)(保整分0s、滚动进 h/d,对齐 CC spinnerSpinnerAnimationRow调formatDuration()无 options=PLAIN 口径,与 CompactionProgress/turnStats/agentStatLine 同约定,非 HUD 的{hideTrailingZeros:true}),门控关(KHY_CC_FORMAT=0)→ 本地手算逐字节回退(${min}m/${min}m ${rem}s)。诚实边界(刻意不纳入)=① 亚秒(<60s)两态字节一致(ccFormatDuration的${Math.floor(n/1000)}s与 legacy${sec}s同),发散只在整分0s("1m 0s"vs"1m")与 ≥1h 进位("1h 2m 3s"vs"62m 3s");② 全部调用方均传秒(已核 spinner/steps/taskMindMap·Math.max(0,…)守负值)不混毫秒;③ HUD 的{hideTrailingZeros:true}是另一约定(60s→"1m"省尾零)不动·本刀只统一 spinner/step/task 这条 PLAIN 链。门控KHY_CC_FORMAT(复用 ccFormat 家族总开关)默认开。test:renderThemeFormatElapsed(node:test)6/6(门控开整分保0s/≥1h 进位/亚秒 byte-identical/与ccFormatDuration(sec*1000)逐项一致/门控关回退本地口径/唯一发散点=整分与 ≥1h)+ spinnerMeta+hudDurationSsot(node:test)13/13 无回归 + taskMindMap(jest)无回归。三守卫过(leaf-contract/agent-rules/model-hardcoding renderTheme.js 净)·TUI 320/320 无回归·bundled 三副本重建(各副本renderTheme.js解析+require('./ccFormat')+门控开 60s→"1m 0s"/3723s→"1h 2m 3s"/门控关→"1m"/"60m" 实证)。GUARDS 红线节。 - [x] 刀22 工具头
name(arg-summary)的 Bashcommand长度上限从统一魔法数 60 改对齐 CCMAX_COMMAND_DISPLAY_CHARS=160(新纯叶子cli/toolHeaderCap.js收敛 per-key 上限·非措辞/路径/折叠) —— 对齐 CC 按工具头展示packages/builtin-tools/src/tools/BashTool/UI.tsx::renderToolUseMessage。背后逻辑:CC 每个工具走自己的renderToolUseMessage,Bash 命令头按MAX_COMMAND_DISPLAY_CHARS=160(另留MAX_COMMAND_DISPLAY_LINES=2多行头)、grep pattern 按TOOL_SUMMARY_MAX_LENGTH=50(src/constants/toolLimits.ts)——都是经过考量的按工具上限。Khytui/ink-components/ToolLines.js::summarizeArgs对每个描述键(command/file_path/path/pattern/query/url/prompt/description/泛型 k=v)一律truncate(...,60)=统一魔法数(无注释标其为刻意宽度)。真缺口=多数 key 的 60 ≥ CC per-tool 上限(grep pattern 60>50,Khy 显得更多非更差)故无须动;唯独 Bashcommand的 60 远低于 CC 的 160,而 Ink TUI 里命令只出现在这条头行(summarizeArgs(t)→name(argSummary)行 526/546·SHELL_*/isShellResult机制全是 RESULT stdout 折叠不含命令本身·不像经典 REPLtoolDisplay.js另有命令模式识别 + 8 行 box 显全貌)→ 61–160 字 Bash 命令被截断在 60 且无处可寻=明显比 CC 差(实证 66 字git log --oneline --graph --all --decorate --abbrev-commit -n 50:CC 显全·Khy 截成…abbrev-comm…)。修(SSOT 纪律)=新纯叶子cli/toolHeaderCap.js:MAX_COMMAND_DISPLAY_CHARS=160(CC 常量)+LEGACY_ARG_CAP=60+argDisplayCap(key,env)(门控开 → command/cmd→160、其余 key→60;门控关 → 全 60 字节回退);summarizeArgs非路径描述键truncate(String(obj[key]), argDisplayCap(key, process.env))。诚实边界(刻意不纳入)=① 只对齐字符上限,不引入 CCMAX_COMMAND_DISPLAY_LINES=2多行命令头(Khy 刻意replace(/\s+/g,' ')单行折叠·刀17 已裁定 NA);② 非 command key 的 60 不降到 CC 的 50(Khy 显得更多非更差·降级=删正确行为违诚实红线);③ 路径键file_path/path仍走truncatePathMiddle(另一轴)不动;④ 经典 REPLtoolDisplay.js命令头是刻意不同的富设计(命令模式识别「git log …」+8 行 box 显全貌·刀17 NA)非本刀对象——故非两 call-site 收敛·本刀仅治 TUI 这一支。门控KHY_TOOL_HEADER_CAP默认开。test:toolHeaderCap(node:test)5/5(常量 160/60·门控开 command/cmd→160 其余→60·门控关全 60·门控梯大小写空白·唯一发散点仅 command/cmd 两态不同其余两态恒 60)+ summarizeArgsPathTruncate(jest)更新为 6/6(原「command 80 字截 60」改「command 200 字端截 160」保 END-truncate 意图+新增「66 字命令门控开显全/关截断 60」)。三守卫过(leaf-contract/agent-rules/model-hardcoding toolHeaderCap.js+ToolLines.js 净)·TUI 320→321/321(+1=path-truncate 套件新增一例)·bundled 三副本重建(各副本toolHeaderCap.js解析+argDisplayCap导出+summarizeArgs门控开 66 字命令显全 cap=160/门控关截断 cap=60 实证)。GUARDS 红线节。 - [x] 刀23 常驻任务面板「已完成(✓)行 strikethrough」对齐 CC
TaskListV2.strikethrough={isCompleted}(新纯叶子cli/tui/ink-components/taskLineStyle.js收敛行样式·非措辞/计数/折叠) —— 对齐 CCsrc/components/TaskListV2.tsx::TaskItem。背后逻辑:CC 渲染<Text bold={isInProgress} strikethrough={isCompleted} dimColor={isCompleted||isBlocked}>{displaySubject}</Text>,对已完成任务标题做strikethrough(划掉),长清单里「已做完的事」一眼可辨被划掉而不只是变暗;图标在另一<Text color>节点(故 CC 只划 subject 不划图标)。KhyTaskListPanel._iconStyle(行 78h(Text,{..._iconStyle(line)},line))对 ✓ 行只给{color:'green',dimColor:true},从不加 strikethrough——已完成项仅暗绿,扫读时与待办难区分。行首图标(✓/→/✗/○)来自_taskStore.snapshot()(_taskStore.js:303✓)/taskPanelState.getTasks()的结构化状态(非模糊推断),确定性可划。真缺口=同一已完成任务在 CC 被划掉、在 Khy 只暗绿 = 明显比 CC 差;且面板挂 App live 区(App.js:1945nowTick1s 重渲染)默认开(KHY_TASK_PANEL默认 on)= 可达;CC 侧isTodoV2Enabled()(tasks.ts:133)交互会话恒 true = 默认 REPL 即渲染。修(SSOT 纪律)=抽纯叶子taskLineStyle.js:taskStrikethroughEnabled(env)+taskLineStyle(line,env)(与历史_iconStyle同源,门控开时 ✓ 行附加strikethrough:true,门控关与历史返回对象逐字节一致),TaskListPanelrequire('./taskLineStyle')消费、删内联_iconStyle、行 78 改...taskLineStyle(line, process.env)。诚实边界(刻意不纳入)=① Khy 面板每行是单个 Text 节点(图标+文本同节点),故 strikethrough 作用于整行(含 ✓ 图标),非如 CC 仅划 subject——渲染结构决定的刻意简化,非缺陷;拆「图标 Text+文本 Text」会改布局且超范围(正是刀19 把 strikethrough 列「暂缓」的顾虑·刀23 改用整行划线绕开拆节点风险而非真去拆);② 仅 completed(✓)加划线对齐strikethrough={isCompleted};in_progress(→)bold、pending(○)dim、error(✗)red 等其余样式不动(Khy cyan/green/red 着色是相对 CC 的增强保留不删);③ CC 对 pending 不 dim、对 blocked 才 dim,Khy 对 pending dim 是可辩护的「弱化未开始项」设计非明显更差不动(改之即删既有刻意选择违诚实红线)。门控KHY_TASK_STRIKETHROUGH默认开。test:taskLineStyle(node:test)6/6(门控开 ✓=green+dim+strikethrough·其余状态不变·门控关 ✓ 回退仅 green+dim 无 strikethrough·门控梯大小写空白·唯一发散点仅 ✓ 两态不同其余恒等·前导空白容忍)+ taskListPanel(ink-tui runner)更新(完成行新增strikethrough===true断言 + 新增门控关strikethrough===undefined回退例)。三守卫过(leaf-contract/agent-rules/model-hardcoding taskLineStyle.js+TaskListPanel.js 净)·TUI 321→322/322(+1=taskListPanel 套件新增门控关一例)·bundled 三副本重建(各副本taskLineStyle.js解析+两导出+门控开 ✓.strike=true/门控关 undefined +TaskListPanelwired=yes 实证)。GUARDS 红线节。 - [x] 刀24 TUI 斜杠命令菜单「过滤/排序」从仅前缀升级到子串/分段/描述匹配,消费既有
rankSlashCommandsSSOT(新领域:TUI 补全菜单过滤·非措辞/计数/折叠/样式) —— 对齐 CCsrc/utils/suggestions/commandSuggestions.ts::generateCommandSuggestions。背后逻辑:CC 用 Fuse 索引,键含命令名(w3)、partKey=命令名按[:_-]切分(w2)、别名(w2)、描述(w0.5),再按 exact>prefix>fuzzy 排序——即子串/分段/描述都能命中,不止前缀。真缺口=Khy TUI 菜单(tui/hooks/useCompletions.js::computeSlash)历史走router.getCompletions→commandRegistry.getCompletions的仅前缀startsWith+字母序.sort();对 Khy 的连字符命令(/autofix-pr//commit-push-pr//subscribe-pr),键/pr在 TUI 完全搜不到(菜单甚至整个消失)——而 Khy 自家经典 REPL 早用repl/slashCommandFilter.js::rankSlashCommands做「前缀>命令子串>标签/描述子串」匹配(/pr经子串命中/autofix-pr)→ TUI 退化到比 Khy 自家 CLI 还差,也比 CC 差。可达=computeSlash是 TUI 输入区每帧补全的唯一斜杠源(KHY_FULL_TUI默认开)。修(SSOT 纪律,消费既有叶子而非另起算法)=抽纯叶子cli/tui/slashMenuFilter.js:slashMenuCommandNames(value,{slashCommands,getCompletionsFn},env)门控开时把 TUI 菜单路由到rankSlashCommands(同一排序内核,与经典 REPL 收敛),门控关时注入式回退到原getCompletionsFn(value)(仅前缀);useCompletions.computeSlash改r.SLASH_COMMANDS+(v)=>r.getCompletions(v)注入消费,菜单项 label/desc 仍由既有slashDescription装配(渲染外观对同一命令完全一致,唯一发散=菜单命令集合与顺序)。诚实边界(刻意不纳入)=① 绝不碰commandRegistry.getCompletions本身——它另喂经典 readlinecompleter(repl.js:846),readline 靠「候选共同前缀」补全,前缀语义在那里是对的,换子串会破坏 readline 补全;本刀只治 TUI 菜单这一支;② 排序/分词/打分全部委托rankSlashCommands(prefix=3/cmd-substring=2/label|desc-substring=1·稳定排序),本叶子不自造评分(否则重复 SSOT);③ 只产命令名有序列表。进一步扫候选 honest-NA:权限确认框选项过滤(PermissionsPrompt是固定 allow/deny 枚举非搜索·无过滤逻辑可对齐)·@-mention 文件补全(computeFile已是startsWith路径前缀·CC 文件补全亦前缀+fs 序·无发散)·thinking block 折叠展示(Khy 无独立 thinking 块渲染面=结构缺失·造则 net-new 非对齐)。门控KHY_TUI_SLASH_SUBSTRING默认开。test:slashMenuFilter(node:test)7/7(门控开/pr子串命中连字符命令+前缀分数在前·门控关逐字节回退前缀路径连字符命令消失·/push→/commit-push-pr·描述/标签子串命中·门控梯·防呆空·/mo前缀按原始下标序)+真路由 e2e(router.SLASH_COMMANDS146 命令:门控开/pr浮出/commit-push-pr+/autofix-pr+/subscribe-pr·门控关空且=== router.getCompletions("/pr"))。三守卫过(leaf-contract/agent-rules/model-hardcoding slashMenuFilter.js+useCompletions.js 净)·TUI 322/322 无回归(useCompletions 无专属 TUI 套件,菜单形状未变)·bundled 三副本重建(各副本 slashMenuFilter.js 解析+两导出+门控开浮出连字符命令/门控关回退 + useCompletions wired=yes 实证)。GUARDS 红线节。 - [x] 刀26 工具失败串「受众拆分」:给「人」显示的入参校验失败串折成单行
Invalid tool parameters,完整分组细节只给「模型」+ Ctrl+O 展开(新纯叶子cli/ccUserFacingToolError.js消费产串方自识别签名ccValidationError.isValidationErrorMessage·与刀「ccValidationError 给模型分组措辞」互补正交·非措辞/折叠口径/计数) —— 对齐 CCsrc/components/FallbackToolUseErrorMessage.tsx。背后逻辑:CC 把工具失败串分两个受众——给模型的 tool_result 保留formatZodValidationError的完整分组串(模型自我纠正的唯一依据),给人的默认(非 verbose)视图if (!verbose && trimmed.includes('InputValidationError: ')) error = 'Invalid tool parameters'折成一行,完整细节仅经全局 Ctrl+O(transcript)展开。真缺口=Khy 在默认 TUI 渲染器(tui/ink-components/ToolLines.js的 isErr 分支 ~line 580)把那条面向模型的多行分组校验串(${tool} failed due to the following issues:\n…)原样铺给人——它落在刀18 折叠阈值(10 行)之内不被折叠,故同一份串既发模型又显给人,从不像 CC 那样按受众拆分(而 Khy 自家ccValidationErrordocstring 早写明那串是「返回给模型…模型自我纠正的唯一依据」)。可达=默认 TUI(KHY_FULL_TUI默认开)工具失败行。修(SSOT 纪律)=① 产串方tools/ccValidationError.js新增自识别判据isValidationErrorMessage(text)(只认自己产的Validation failed: …与… failed due to the following issue(s):两形;签名由产串方 SSOT 拥有,与 CC 凭includes('InputValidationError: ')识别自家校验错误同一原理·非模糊启发式);② 新纯叶子cli/ccUserFacingToolError.js::collapseValidationErrorForDisplay(text,{expanded},env)——门控开 + 折叠态 + 是校验串 →Invalid tool parameters(COLLAPSED_VALIDATION_TEXT);展开(Ctrl+O)/非校验串/门控关/非串 → 原样;③ToolLines.jsisErr 分支把errorText(t.result)产物先过此叶子(传{ expanded })再交既有planErrorFold(刀18 折叠)。诚实边界(刻意不纳入)=① 只折叠本仓自产校验串;非校验类失败(权限被拒绝、bash 退码、网络错误等)逐字节原样透传(对人有信息量·CC 也只折校验类);② 可恢复:折叠后完整细节仍可经既有全局 Ctrl+O(expanded)看到(=CC 的 transcript 拆分)·不丢信息;③ 模型侧 tool_result 不动(本刀只在ToolLinesdisplay 层调用·不碰tools/index.js的result.error);④ 不接经典 readline REPL(repl.js:7636那支已.slice(0,120)把多行折成单行截断=不同的既有行为·且其 isErr 分支无_expandableOutputs注册→折叠会无 Ctrl+O 恢复=不诚实·故诚实留 honest-NA);⑤ 不引入 CC 的Error:前缀归一(改动面遍及所有工具错误、Khy 错误本地化措辞各有设计·非清晰缺口·刻意不做)。门控KHY_USER_FACING_TOOL_ERROR默认开·关/expanded→ 原样返回逐字节回退(显完整分组)。test:ccUserFacingToolError(node:test)16/16(产串方自识别签名 4 例·门控梯·折叠态校验串→单行·legacy 也折·展开→完整保todos·非校验类原样·门控关字节回退·默认开·防呆非串/opts 缺省·开关唯一分歧=校验串)+ ccValidationError(node:test)10/10 + builtinSchemaValidation(jest)4/4 无回归。三守卫过(leaf-contract ccUserFacingToolError.js+ccValidationError.js 净·agent-rules/model-hardcoding ToolLines.js 净)·TUI 322/322 无回归·端到端 pipeline 探针(模型 full 不变 / 门控开折叠→单行 / 展开→full / 门控关→字节回退)·bundled 三副本重建(4 文件×3=12 byte-identical + 各副本 runtimeon="Invalid tool parameters" off==full)。GUARDS 红线节。 - [x] 刀27 Read 工具头行路径后追加「读取范围」后缀(
offset/limit→第 X-Y 行/从第 X 行起),新纯叶子cli/readRangeSuffix.js收敛两处头渲染,绝不再把 read 真实携带并消费的 offset/limit 整段丢弃只回显裸路径(新领域:工具头「读取范围」算术·非措辞/计数/折叠/样式) —— 对齐 CCpackages/builtin-tools/src/tools/FileReadTool/UI.tsx::renderToolUseMessage。背后逻辑:CC 在 Read 头行路径之后用一段具体算术折出行区间:const startLine = offset ?? 1; const lineRange = limit ? \lines ${startLine}-${startLine + limit - 1}\: \from line ${startLine}\`,即Read(foo.ts · lines 40-80)(offset+limit)/Read(foo.ts · from line 40)(仅 offset)。**真缺口**=Khy 两处工具头渲染——经典 REPLtoolDisplay.js:424read 分支return params.file_path||…、默认 TUIToolLines.js::summarizeArgs命中 file_path/path 即返截断路径——**都只回显裸路径**,把src/tools/readFile.jsinputSchema 真实声明(offset/limit)并在第 105-109 行据此切行区间消费的数据**整段丢弃**:模型分页读大文件某一段时,用户看不出读了哪一段。**可达**=大文件分页读(readFile 自带 offset/limit pagination hint,模型常用)。**修(SSOT 纪律)**=新纯叶子cli/readRangeSuffix.js::buildReadRangeSuffix(params,env)移植 CC 那段算术(startLine + limit - 1、无 limit 时从第 N 行起回退;只认**正整数** offset/limit,数字串也接受);两处头渲染在路径串之后+ buildReadRangeSuffix(...)(TUI 侧再以isReadToolName(tool.name||toolName||tool)门控只对 read 生效,write/edit/grep 不追加)。**诚实边界(刻意不纳入)**=① **绝不移植 CC 的pages分支**(Khy read **无**pages参数,凭空造· pages N= 臆造 Khy 不携带的数据→违诚实红线);② offset/limit **无条件**展示(不做 CC 的 verbose 门控——Khy 无 verbose 概念,且行区间是「读了哪一段」的实质信息而非冗余,大多数读不设 offset/limit 故不嘈杂·与 CC 自身**总是**展示pages分支同取向,只落到 Khy 携带的行区间数据上);③ 只认正整数(0/负/小数 floor/非数/NaN/Infinity 忽略·非对象参数→空后缀不抛)。门控KHY_READ_RANGE_SUFFIX默认开·关 → 返回''头行只剩裸路径逐字节回退。test:readRangeSuffix(**node:test**)16/16(门控梯·isReadToolName归一·offset+limit→第 X-Y 行·仅 limit→start 回退 1·仅 offset→从第 N 行起·非正整数忽略·数字串接受·绝不产 pages·门控关字节回退·防呆非对象)无回归。三守卫过(leaf-contract/agent-rules/model-hardcoding readRangeSuffix.js 净)·TUI **322/322** 无回归(既有 summarizeArgs 测试用非 read 工具或无 offset/limit→后缀恒空字节不变)·端到端 TUI 渲染探针(read+offset+limit→src/x.js · 第 40-80 行·write+offset+limit→裸路径无后缀·read 无范围→裸路径·门控关→字节回退裸路径)·bundled 三副本重建(3 文件×canonical=byte-identical + 各副本 runtimeread+range="… · 第 40-80 行" write==裸路径`)。GUARDS 红线节。 - [x] 刀28 常驻任务清单面板头行从静态
任务清单升级为「按状态计数」标题(total + 完成 + 进行中(仅 >0) + 待办 + 错误(仅 >0)),新纯叶子cli/tui/ink-components/taskPanelHeader.js消费taskPanelLines.countTaskLinesByStatus(图标→状态逆映射 SSOT)·计数覆盖全量(可见 + 隐藏)(新领域:任务面板头行计数·非措辞/折叠/样式) —— 对齐 CCsrc/components/TaskListV2.tsx的isStandalone头行。背后逻辑:CC 标准任务清单(<TaskListV2 isStandalone>,挂REPL.tsx)算completedCount/pendingCount/inProgressCount = tasks.length - completed - pending,头行{tasks.length} tasks ({completedCount} done, [{inProgressCount} in progress, ]{pendingCount} open)——total 与 done/open 总在,in-progress 仅inProgressCount > 0才出现一段。真缺口=KhyTaskListPanel.js:68头行只渲染静态h(Text,{dimColor:true},'任务清单'),没有任何 total/完成/进行中/待办分解——用户看不出清单整体进度(几项、几项已完成、几项在跑)。可达=默认 TUI(KHY_FULL_TUI默认开)输入框上方常驻任务面板,coordinated(App 传lines+hidden+hiddenLines)与 standalone(自读_taskStore+taskPanelState)两模式都经此头行。修(SSOT 纪律)=①taskPanelLines.js新增countTaskLinesByStatus(lines)——复用既有 module-privateICON_STATUS(✓完成/→进行中/✗错误/○待办)逆映射,按行首图标计数,未知图标计入unknown(不另立第二份图标表);② 新纯叶子cli/tui/ink-components/taskPanelHeader.js::buildTaskPanelHeader({lines,hidden,hiddenLines},env)移植 CC 计数(完成/待办总在·进行中仅 >0·khy 诚实扩展 error(✗)仅 >0——CC 把非 completed/pending 一律并进 in-progress,khy 有显式 error 状态故单列更诚实);③TaskListPanel.js:68用buildTaskPanelHeader(...) || '任务清单'装配头行。诚实边界(刻意不纳入)=① 计数源是已渲染行的行首图标(panelStatusIcon写入的真实结构),不是模糊启发式;② 计数必须覆盖全量(可见lines+ 全部hiddenLines)——hidden>0却拿不到等长hiddenLines(无法逐行归类)、或任一行行首非已知图标(unknown>0)→buildTaskPanelHeader返''→ 回退静态任务清单,绝不少计/错计/把未知行静默并进某一类;③ 与刀19 隐藏摘要行(⋯ 仅显示末尾 N 项(另有 …))互补不重复:头行给全量按状态总计,刀19 行给「可见尾切 vs 隐藏」的展示告知;④ 进行中/错误仅 >0 出现(对齐 CCinProgressCount>0)。门控KHY_TASK_PANEL_HEADER默认开·关 →buildTaskPanelHeader返''头行回退静态任务清单逐字节回退。test:taskPanelHeader(node:test)18/18(门控梯·完成待办总在/进行中仅 >0·error 仅 >0·全完成·coordinated 全量计数·hidden 数不符回退·hidden=0 仅可见·未知图标回退·隐藏行未知图标回退·空清单·门控关回退·防呆非对象·total===lines+hidden)无回归。三守卫过(leaf-contract/agent-rules/model-hardcoding taskPanelHeader.js+taskPanelLines.js 净)·TUI 322→323/323(+1=taskListPanel 套件新增门控关KHY_TASK_PANEL_HEADER=0回退静态任务清单一例;另更新 3 例既有断言从静态标签改为计数头行)·端到端探针(门控开头行带计数·门控关''·coordinated 可见+隐藏全量计数)·bundled 三副本重建(3 文件×canonical=byte-identical + 各副本 runtimeon="任务清单(共 4 项:…)" off="" hid=可见+隐藏全量计数)。GUARDS 红线节。 - [x] 刀29 任务快照行「blocked by」后缀只列仍未完成的依赖,已完成的依赖剔除、全部完成则后缀整段消失,新纯叶子
tools/taskBlockedBySuffix.js收敛_taskStore.snapshot()那段后缀构造,绝不再把原始blockedBy.join(',')原样铺出(新领域:任务行依赖注解的「过期阻塞过滤」·非措辞/计数/折叠/样式) —— 对齐 CCsrc/components/TaskListV2.tsx的openBlockers。背后逻辑:CC 算unresolvedTaskIds = new Set(tasks.filter(t => t.status !== 'completed').map(t => t.id)),openBlockers = task.blockedBy.filter(id => unresolvedTaskIds.has(id)),渲染isBlocked && (… blocked by {[...openBlockers].sort((a,b)=>parseInt(a)-parseInt(b)).map(id=>'#'+id).join(', ')})——依赖一旦完成就从「blocked by」注解剔除,全部完成则isBlocked=false整段消失,展示的永远是当前仍在阻塞的依赖;survivors 数字序 +#前缀。真缺口=Khy_taskStore.snapshot():304把原始blockedBy数组join(',')原样铺出([blocked by: 1,3]),依赖完成后仍把已完成的阻塞显示出来 → 过期、误导(任务其实已可启动却仍标着「blocked by」)。注意 khy 自家canStart():93早有同款完成判据_legacyStatus(dep.status) !== 'completed',故快照层显示与 claim 层判定自相矛盾=真缺陷非刻意。可达=快照消费者:压缩管线(compactPipeline/contextCompressor喂模型上下文)与常驻 TUI 任务面板 standalone 模式(TaskListPanel经mergeTaskLines)。修(SSOT 纪律)=① 新纯叶子tools/taskBlockedBySuffix.js::buildBlockedBySuffix(blockedBy, completedIds, env)过滤completedIds命中者、#前缀 + 数字序(非数字 id 退稳定字典序,khy id 可为t-xxx形);②snapshot()用既有list()一次性建completedIds = new Set(tasks.filter(status==='completed').map(t=>String(t.id))),逐行调叶子替换内联三元。诚实边界(刻意不纳入)=① 只剔除确已完成(completedIds命中)的依赖;缺失/不存在的依赖 id 保留显示(与 khy 自家canStart():92的「missing dep = blocked」语义一致、对悬空依赖是有用告警)——这是相对 CCunresolvedTaskIds只含存在任务的刻意诚实分歧,绝不静默吞掉悬空依赖(照搬 CC 会把悬空依赖也滤掉=丢失告警);② 纯确定性过滤/排序,无模糊启发式;③ 门控关或非数组/空数组 → 逐字节回退历史[blocked by: ${blockedBy.join(',')}](未过滤、无#、逗号无空格)。门控KHY_TASK_BLOCKED_BY_FILTER默认开·关 →buildBlockedBySuffix原样join(',')逐字节回退。test:taskBlockedBySuffix(node:test)11/11(门控梯·空/非数组·无完成全列·已完成剔除·全完成→空·数组转 Set·悬空依赖保留·数字序+字典序·数字归一·门控关字节回退·两态分歧)+ todoSnapshot(jest)13/13(新增 snapshot 完成依赖剔除 / 全完成后缀消失 /KHY_TASK_BLOCKED_BY_FILTER=0字节回退原始 join 三例)无回归。三守卫过(leaf-contract taskBlockedBySuffix.js 净·agent-rules/model-hardcoding taskBlockedBySuffix.js+_taskStore.js 净)·TUI 323/323 无回归(panel 测试 mock 掉 snapshot 不受影响)·taskDependencyGate(jest)100/100 无回归·端到端探针(四树一致:门控开[blocked by: #1](滤 #3)·门控关[blocked by: 3,1](原样)·全完成'')·bundled 三副本重建(taskBlockedBySuffix.js(新)/_taskStore.js × canonical=byte-identical + 测试文件同步 + 各副本 runtimeon/off/gone一致)。GUARDS 红线节。 - [x] 刀30 常驻任务清单超高截断从「盲目尾切」改「按状态生存优先级保活」(进行中/错误 > 待办 > 已完成),新纯叶子
taskPanelLines.selectTaskLinesByPriority收敛liveRegionBudget.capTaskLines的截断重排,绝不再盲目arr.slice(cut)把非末尾的进行中任务挤掉(新领域:任务清单截断的「按状态优先存活」·非措辞/计数/折叠/样式) —— 对齐 CCsrc/components/TaskListV2.tsx的截断优先级。背后逻辑:CC 截断长清单前先按状态重排prioritized = [...recentCompleted, ...inProgress, ...pending, ...olderCompleted]再visibleTasks = prioritized.slice(0, maxDisplay)——保证「进行中/待办」优先于「陈旧已完成」存活,用户最该看见的活动任务绝不被长清单挤出显示。真缺口=KhyliveRegionBudget.capTaskLines把超clamp(floor(rows*0.30),3,10)的清单盲目尾切(arr.slice(cut)留末尾 cap 行),一个非末尾的→(进行中)任务后面跟一串末尾已完成项时,进行中任务会被尾切丢掉——而它正是用户最该看见的。可达=默认 TUI(KHY_FULL_TUI默认开)输入框上方常驻任务面板,App.js coordinated 模式经capTaskLines封顶后传lines+hidden+hiddenLines。修(SSOT 纪律)=①taskPanelLines.js新增selectTaskLinesByPriority(lines, cap)——按_SURVIVAL_RANK {in_progress:0, error:1, pending:2, completed:3}升序选存活的 cap 行(复用既有ICON_STATUS图标→状态逆映射 SSOT,不另立第二份图标表),同档内降序原始下标=守 khy 既有「尾锚定」哲学(liveRegionBudget注释的「尾切=实时仅显示末尾」),最后按原始顺序恢复显示;②liveRegionBudget.capTaskLines门控开且全部行图标可识别 → 调它,否则回退历史slice尾切;③TaskListPanel.js尾切提示措辞经taskPriorityCapEnabled门控:开 → 去「末尾」二字(留下的是优先级最高项非末尾项),关 → 回退「仅显示末尾 N 项」。截断条数cut不变 → 面板高度恒等 →resolveStreamReserve预算不变(高度中性,不破坏 scroll-jank reserve 不变式)。诚实边界(刻意不纳入)=① khy 任务行不携带完成时间戳,无法区分 CC 的recentCompleted(≤30s)与olderCompleted,故把全部已完成归最低生存优先级,绝不臆造时间戳谎称「最近完成」(照搬 CC 的 30s 分桶=伪造 khy 不持有的数据=违诚实红线);②error(✗)是 CC 不具的 khy 显式状态,归入高优先级(失败需被看见);③ 任一行行首图标不可识别(非 ✓/→/✗/○)→selectTaskLinesByPriority返null→capTaskLines回退历史尾切(绝不在不确定状态下错排/误隐);④ 门控关 → 逐字节回退arr.slice(cut)尾切 + 措辞回退「末尾」。门控KHY_TASK_PRIORITY_CAP默认开·关 → 历史盲目尾切逐字节回退。test:taskPanelLines+liveRegionBudget(node:test)43/43(selectTaskLinesByPriority:进行中救回·同档尾锚定·跨档优先级·不可识别 null·无截断 null·trimStart;taskPriorityCapEnabled门控梯;capTaskLines:优先级救回非末尾进行中·全同档=历史尾切·门控关回退尾切·不可识别回退尾切)+ TUI 套件(323→324/324,+1=taskListPanel.test.js新增KHY_TASK_PRIORITY_CAP=0回退「末尾」措辞一例;另更新 3 例既有断言默认开去「末尾」)。三守卫过(leaf-contract/agent-rules/model-hardcoding taskPanelLines.js+liveRegionBudget.js+TaskListPanel.js 净)·端到端探针(四树一致:门控开救回→ ip·门控关尾切✓ c7丢→ ip)·bundled 三副本重建(3 源文件+3 测试文件 × canonical=byte-identical + 各副本 runtime on/off 一致)。GUARDS 红线节。 - [x] 刀31 diff/预览「溢出折叠标记」的文本构造 + 英文复数守卫收敛到新纯叶子
cli/previewOverflowMarker.js,两套独立渲染引擎(Ink TUIToolLines.js+ 经典 REPLtoolOutputRender.js)共用,N=1时不再输出语法错的「+1 lines」「1 more row」(对齐 CC 处处守=== 1的显示约定;新领域:溢出标记文本 + 复数守卫·非措辞/计数格式/折叠/样式) —— 背后逻辑:CC 显示约定是所有可数名词处处守=== 1 ? singular : plural。真缺口=这条「头部预览 + 隐藏其余 N 行,ctrl+o 展开」(展开态改说(capped))的溢出标记被两套独立渲染引擎逐字重复:Ink TUItui/ink-components/ToolLines.js::buildWriteDiffRows(moreText(n,sign)单文件 add/del 预览溢出 + 多 hunk 行数封顶... (diff truncated, N more rows, …))与经典 REPLrepl/toolOutputRender.js(新建/删除文件预览... ±N lines (ctrl+o to expand)),两处都把lines/rows写死复数,N=1(预览恰好溢出一行/截断恰好一行)时输出语法错的「+1 lines」「1 more row」。而 khy 自己紧挨着的 gap 分隔行(ToolLines.js:217line${gapBefore!==1?'s':''})和diffRenderer.js:388早已守复数——同一函数里守了 gap、漏了 overflow。注:刀27 扫候选时曾把此族判 NA,称「已正确守卫单复数(diffRenderer.js:388/ToolLines.js:209)」——但那次扫描只核了 gap 分隔行,漏看了同函数的moreText/rows溢出标记;刀31 正是补这次扫描的盲点(诚实修正,非推翻 NA)。可达=默认 TUI 写文件 diff 预览(buildWriteDiffRows)与经典 REPL(KHY_FULL_TUI=0)新建/删除文件预览,溢出量恰为 1 时。修(SSOT 纪律)=新纯叶子buildLinesOverflow(n, sign, expanded, env)(... ±N line(s) (ctrl+o to expand|capped))+buildRowsOverflow(dropped, expanded, env)(... (diff truncated, N more row(s), …)),收敛标记文本 + 复数守卫,两引擎共用;各 call-site 仍自管缩进(经典 REPL前缀)与着色(c.dim)。诚实边界(刻意不纳入)=① 门控开N>1与历史逐字节一致(本就复数),N===1守单数(+1 line/1 more row)=唯一可见分歧;门控关 → 恒复数(legacy 字节回退);② 刻意不纳入toolDisplay.js:961的+N more tool uses(另一族 marker:经典 REPL agent 工具用量计数,非 diff/预览溢出),留作后续候选,绝不顺手扩进无关 marker;③ 已正确守复数的ToolLines.js:217/diffRenderer.js:388/editStatLine.js/diffViewer.js各自内联!== 1 ? 's'不动(把已正确的代码收敛进叶子=无可见输出变更的空重构,违诚实红线);④sign只认-其余归+,非有限n→ 0。门控KHY_PREVIEW_OVERFLOW_PLURAL默认开·关 → 恒复数逐字节回退。test:previewOverflowMarker(node:test)14/14(门控梯·折叠/展开 N>1 字节一致·N===1守单数 line·row·门控关恒复数回退·两态唯一分歧=N===1 单复数·sign 归一·非有限→0)+ TUI 套件(324/324,toolLinesWriteDiff折叠态/ctrl\+o/断言 N=5 不受影响)+node --test services/backend/tests/writeDiff.test.js(29)无回归。三守卫过(leaf-contract/agent-rules/model-hardcoding previewOverflowMarker.js 净·两 call-site leaf-contract 净)·端到端探针(四树一致:门控开 N=1+1 line·门控关+1 lines·rows1 more row)·bundled 三副本重建(previewOverflowMarker.js(新)/ToolLines.js/toolOutputRender.js + 测试文件 × canonical=byte-identical + 各副本 runtime on/off 一致)。GUARDS 红线节。 - [x] 刀32 经典 REPL 工具追踪器「+N more tool use(s)」溢出标记的 use/uses 复数守卫,扩既有 SSOT
agentStatLine.agentMoreToolUsesLabelOr(不新建叶子),toolDisplay.js:961单 call-site 改走它,hiddenCount===1时不再输出语法错的「+1 more tool uses」(对齐 CCAgentTool/UI.tsx:639+{hiddenToolUseCount} more tool {hiddenToolUseCount === 1 ? 'use' : 'uses'};承刀31 刻意留作后续的候选) —— 背后逻辑:CC 处处守=== 1 ? singular : plural。真缺口=ToolUseTracker运行态(_tools.length > _maxVisible,默认 maxVisible=3)在末尾打印隐藏计数toolDisplay.js:961,khy 历史写死复数+${hiddenCount} more tool uses,hiddenCount === 1(恰 4 个工具)时输出「+1 more tool uses」。SSOT 纪律(关键:不新建叶子)=该 file 的「tool use(s)」复数早由cli/agentStatLine.jsSSOT(agentToolUsesLabelOr,门控KHY_CC_FORMAT)owns(刀12 落地·已接 5 处Done (N tool uses…)统计行),本刀只扩 SSOT 加agentMoreToolUsesLabelOr(count, legacy, env)(同一 CC=== 1守卫、同一KHY_CC_FORMAT门控、同一*Or(value, legacy, env)约定),toolDisplay.js:961单 call-site 改走它。诚实边界(刻意不纳入)=① 门控开count>1与历史逐字节一致(本就复数),count===1守单数+1 more tool use=唯一可见分歧;门控关 → 恒 call-site legacy+${count} more tool uses(字节回退);② 复用KHY_CC_FORMAT(不新设门控)=与 agentStatLine 全族一致的刻意选择(该 marker 单复数本就是同一 CC tool-use 显示约定);③ 不把agentToolUsesLabelOr(返回整句1 tool use/legacy)硬塞进带+…more前缀的标记(语义不同),也不另起全局 pluralize 工具(收敛别处已正确的内联守卫=空重构,违诚实红线)。门控KHY_CC_FORMAT默认开·关 → 恒复数逐字节回退。test:agentStatLine(node:test)16/16(新增agentMoreToolUsesLabelOr门控开 count===1 守单数 / count>1 legacy / 门控关恒 legacy / 默认门控)+ 消费者回归agentTreeView·agentTreeController·agentStatsService(jest 48/48)+ 端到端 ToolUseTracker 探针(门控开 4 工具 →+1 more tool use·门控关 →+1 more tool uses)。三守卫过(leaf-contract/agent-rules/model-hardcoding × agentStatLine.js+toolDisplay.js 净)·bundled 三副本重建(agentStatLine.js/toolDisplay.js/agentStatLine.test.js × canonical=byte-identical + 各副本 runtime on/off/N>1 一致)。GUARDS 红线节。 - [x] 刀33 上下文窗口占用率喂入值漏算缓存段,新纯叶子
cli/contextResidentTokens.js::contextResidentTokensOr(tokenUsage, legacyBase, env)把cacheReadInputTokens+cacheWriteInputTokens加回未缓存inputTokens基数,两处setContextUsage(ai.js:6286、repl.js:6784)改走它(对齐 CCsrc/utils/context.ts::calculateContextPercentages的totalInputTokens = input_tokens + cache_creation_input_tokens + cache_read_input_tokens) —— 背后逻辑:占用率分子必须三段都算,命中的缓存 token 仍占据上下文窗口、只是计费便宜,没从窗口消失。真缺口=两处setContextUsage只喂未缓存tokenUsage.inputTokens一段,把同一对象上早已携带的cacheReadInputTokens/cacheWriteInputTokens(经services/gateway/adapters/_cacheUsage.js自cache_read_input_tokens/cache_creation_input_tokens规范而来)整段丢弃(repl.js 注释还写死错误心智模型「actual tokens sent as context」)→ 开 prompt cache 的 Claude-Direct 链路上,一轮缓存命中 turninputTokens:1.2k+cacheRead:78k占用率被低估一个数量级(实测 1% 应为 41%):HUD 上下文条整段会话停近空绿、/context面板显 1%、且喂同一state.contextWindow.used的自动压缩告警(contextWarning,repl.js:1831/1895)几乎永不触发。全 5 处消费者共源=HUD 条(hudRenderer.renderContextBar)/两处自动压缩(buildContextWarning)//context面板(repl.js:3837+router.js:1327的computeContextStats)都读hud.getState().contextWindow.used,故单点修喂入值即修全部。SSOT 纪律=无既有叶子求这三段和(别处totalInputTokens指会话累计非窗口占用),新建纯叶子(零 IO 零业务 require、确定性、*Or(value,legacy,env)镜像 agentStatLine/ccFormat 约定):门控开→legacyBase + 读缓存 + 写缓存,门控关→原样legacyBase;三段在 Anthropic usage 互不相交(khystate.totalInputTokens += input_tokens只累未缓存段佐证)相加不重复计数。诚实边界=① 真缺口(数据在手只漏算)非臆造不持有数据;② 只动喂入值不动 call-siteif(inputTokens>0)触发守卫(真实 turninputTokens几无 0·改守卫=扩面);③ 门控关→各 call-site 逐字节回退原inputTokens(ai.js 传tokenUsage.inputTokens、repl.js 传inputTokens||promptTokens||0,故由 call-site 传 base 而非叶子写死,字节回退绝不串味);④computeContextStats的round(used/limit*100)公式本就与 CC 一致不动,本刀只修被喂进的used。门控KHY_CONTEXT_CACHE_TOKENS默认开·关→恒 legacyBase 逐字节回退。test:contextResidentTokens(node:test)12/12(门控梯 / 三段相加 81.2k / 无缓存=legacyBase / 仅读缓存 / 门控关恒 legacyBase / 唯一分歧=缓存段 / 防呆 base 非数·缓存段负·null usage / 默认门控)+ 端到端setContextUsage→computeContextStats探针(门控开 81200→41% healthy·门控关 1200→1%)+hudTokensSsot(node:test)3/3 无回归(直喂字面值不经 call-site)。三守卫过(leaf-contract/agent-rules/model-hardcoding × contextResidentTokens.js+ai.js+repl.js 净·repl.js agent-rules 1warn 预存)·bundled 三副本重建(contextResidentTokens.js/ai.js/repl.js/测试 × canonical=byte-identical + 4 树 require 一致)。GUARDS 红线节。下一候选(part b/c 续):上下文占用域已收口(刀33);单/复数/计数标记族已穷尽(刀31/32);回 part b 前端逐项对齐或新探更高价值显示逻辑发散。 - [x] 刀34 权限对话「始终允许(allow-always)」选项标签的动词对所有工具硬编码成 "reading",新纯叶子
cli/ui/alwaysAllowLabel.js::buildAlwaysAllowLabelOr(toolName, legacyLabel, env)按工具族选动词,单工具对话cli/ui/permissionDialog.js:612改走它(对齐 CC 按工具族分流权限对话:FilePermissionDialog/permissionOptions.tsx写/编辑→"all edits"、BashPermissionRequest/bashToolUseOptions.tsxbash→命令族) —— 背后逻辑:allow-always实际授予permissionStore.approve(permissionKey,'forever')(永久放行该工具),option-2 标签必须忠实描述该工具的真实操作。真缺口=permissionDialog.js单工具对话把 option-2 标签硬编码成Yes, allow reading from <project> from this project,对所有工具(含 Write/Edit 改文件、Bash 任意命令如rm -rf)都说 "reading",把改写/危险操作描述成只读授权(用户可见的危险误导)。SSOT 纪律=新纯叶子(零 IO、零业务 require、零 chalk、确定性、*Or(value,legacy,env)镜像 agentStatLine/ccFormat 约定),按formatToolContent同口径归一(toLowerCase().replace(/[\s_-]/g,''))选动词:写/编辑族(write/edit/multiedit/notebookedit/scaffold…)→"all edits"、bash 族(bash/shellcommand/command)→"running commands"、只读类(read/grep/glob/ls/webfetch)→保留 legacy "reading";叶子返回含{project}占位符的纯字符串,call-site.replace('{project}', chalk.bold(projectName))(chalk 仍在 call-site,叶子纯净)。诚实边界=① 真缺口(同一标签对改写/危险操作谎称只读)非 no-op;② khy 无法复刻 CC 的 Haiku 实时生成命令前缀建议(模型调用·伪造前缀=臆造)→ bash 只用真实通用措辞 "running commands"(忠实描述「永久放行 bash 工具」),绝不臆造 per-command-prefix 作用域;③ 只覆盖明确错且危险的两族(写/编辑、bash),与 CC 两个专用对话同构·read/search/fetch 等只读类 "reading" 本就成立门控开也不动;④formatBatchPermissionDialog的 "always trust from <project>" 本就动词中性不纳入。门控KHY_ALWAYS_ALLOW_LABEL默认开·关→原样返回 call-site 传入 legacyLabel(逐字节回退)。test:alwaysAllowLabel(node:test)10/10(门控梯 / 写编辑族→all edits 含归一变体 / bash 族→running commands / 只读类保留 reading / 门控关恒 legacy / legacy 原样透传不串味 / 防呆 null·undefined·数字 toolName / 默认门控)+ 端到端 call-site.replace探针(门控关 write/bash/read 三族 byte-identical 现状·门控开 write→"all edits"·bash→"running commands"·read→"reading")。回归:permissionDialogResolve+permissionsPromptL2Options+permissionReply(node:test)20/20 无破坏(全树无任何测试断言 option-2 标签文本)。三守卫过(leaf-contract/agent-rules/model-hardcoding × alwaysAllowLabel.js+permissionDialog.js 净)·bundled 三副本重建(alwaysAllowLabel.js/permissionDialog.js/测试 × canonical=byte-identical sha256 + 4 树 require 一致)。GUARDS 红线节。下一候选(part b/c 续):权限对话动词域已收口(刀34);回 part b 前端逐项对齐或新探更高价值显示逻辑发散。 - [x] 刀35 spinner token byline 数字格式
cli/spinner.js:167内联/1000.toFixed(1)是 token 格式器同族迁移漏掉的最后一支孤儿,直接 route 进既有ccFormatTokensSSOT(对齐 CCSpinnerAnimationRow.tsx的formatNumber) —— 背后逻辑:token 数字应紧凑记数(>10k不钉 ".0"、百万级进 "m")。真缺口=spinnerfmtTokens闭包旧规则n>=1000 → (n/1000).toFixed(1)+'k'elseString(n)会显45.0k(应45k)、128.0k(应128k)、且无 mega 单位停在1500.0k(应1.5m);而同族其余 call-site(hudRenderer.fmtTokens/turnStats.fmtTokens/agentTokensLabelOr)早已迁ccFormatTokens——hudRenderer.fmtTokens注释甚至自证「旧本地格式器 dropped the decimal above 10k 且 no mega unit」=本刀正是补该注释点名的孤儿。SSOT 纪律(关键)=不新建叶子(ccFormatTokens已是 SSOT,新建=重复违红线),直接把 spinner 内联闭包 route 进ccFormatTokens,镜像hudRenderer.fmtTokens的 wrapper(ccFormatEnabled(process.env)判→ccFormatTokens(n),非有限''回退 legacy)。诚实边界=① 真缺口(孤儿,非 no-op);② 收敛到既有 SSOT 非新建重复;③spinnerMeta.buildStatusParts收的是已格式化字符串('↑ 1.2k tokens'),本刀只改喂入前的数字格式器,assembly 层不动→spinnerMeta.test.js传预格式化串故无回归;④ 门控关 / 非有限 → 逐字节回退 spinner 自己的历史规则(n>=1000 → toFixed(1)kelseString(n),不串入hudRenderer略不同的n>=10000 → toFixed(0)legacy,避免跨 call-site 串味)。门控KHY_CC_FORMAT(同族门控)默认开·关→逐字节回退。验证:spinnerMeta.test.js(node:test)9/9 assembly 无回归 + 门控关 byte-identity 探针(KHY_CC_FORMAT=off时 wrapped(n) === spinner legacy(n) 对 5/500/1000/45000/128000/1500000 全等)+ 门控开发散探针(45000→"45k"·128000→"128k"·1500000→"1.5m")+ spinner.js require/parse OK。守卫过(agent-rules/model-hardcoding × spinner.js 净;spinner.js 非纯叶子不跑 leaf-contract)·bundled 三副本重建(spinner.js × canonical=byte-identical sha256 + 4 树 require 一致)。无新叶子无新测试(SSOTccFormatTokens已测、assemblyspinnerMeta已测)。GUARDS 红线节。下一候选(part b/c 续):token 数字格式器家族已彻底收口(刀35 补完最后孤儿);回 part b 前端逐项对齐或新探更高价值显示逻辑发散。 - [x] 刀36
/context//status面板的裸 token 数字散落router.js:1338-1340(英文三行)+repl.js5 处中文面板内联(x/1000).toFixed(1)k(limittoFixed(0)k),收敛进既有 SSOT 模块ccFormat新增的标准*Or兄弟ccFormatTokensOr(count, legacy, env)(对齐 CC 上下文面板的formatTokens) —— 背后逻辑:面板 token 数字应紧凑记数(>10k不钉 ".0"、百万级进 "m"),与刀35 spinner 同口径同门控。真缺口=router.js的Used/Remaining/Session三行 +repl.js的已使用/剩余/会话令牌/令牌用量多处显Used: 128.0k(应128k)、1M 上下文 limit 显1000k(应1m),与早已迁ccFormatTokens的 spinner/hud/turnStats 族不一致。SSOT 纪律(关键)=混合 legacy(.toFixed(1)k数据图 /.toFixed(0)klimit 图)需 per-site legacy,故走*Or(value, legacy, env)约定(镜像ccFormatCostOr/ccBriefTimestampOr);ccFormatTokens此前无 Or 变体、agentTokensLabelOr烤死 " tokens" 后缀不可裸嵌→给ccFormat加标准ccFormatTokensOr(门控开→ccFormatTokens(count),非有限''回退 legacy;门控关→原样返 legacy)=收敛到既有 SSOT 模块非新建重复叶子。router.js用两个局部tk1/tk0、repl.js用模块级_tk1/_tk0/_tkSpin(避免 5 处重复定义)把全部 token 图 route 进它。诚实边界=① 真缺口(与刀35 同类·token 数字未走 SSOT)非 no-op;② 收敛到既有 SSOT 模块非新建重复;③/context面板布局(中文标签、无 CC 的⛁/⛀/⛶方格网格)是 khy 刻意不同设计·本刀只动数字 humanize 口径不碰布局(照搬网格需 category 分解数据 khy 不携带=臆造·刻意不做);④ 仅 token 图(k 后缀);时长(s后缀,如elapsed/1000).toFixed(1)s)绝不纳入(那是ccFormatDuration族另一刀);⑤ 门控关→各 call-site 逐字节回退自己的 legacy(tk1=.toFixed(1)k、tk0=.toFixed(0)k、_tkSpin=n>=1000?toFixed(1)k:String(n)·绝不串味)。门控KHY_CC_FORMAT(同族门控)默认开·关→逐字节回退。验证:ccFormat.test.js(node:test)14/14(含ccFormatTokensOr门控开裸数字 45000→"45k"/128000→"128k"/1500000→"1.5m"/200000→"200k" + 门控关 call-site legacy 逐字节 + 非有限回退 + 默认门控)+ 三 helper 门控关 byte-identity 探针(tk1/tk0/_tkSpin对 0/500/1000/12000/45000/200000/1000000/1500000 全等原.toFixed)+ token 消费者回归 19/19(hudTokensSsot/tokenCountReportSsot/ccFormatCost)。守卫过(leaf-contract × ccFormat.js 净·agent-rules/model-hardcoding × router.js/repl.js 净;repl.js agent-rules 1warn=no-opaque-status@3191 预存非本刀引入)·bundled 三副本重建(ccFormat.js/router.js/repl.js/ccFormat.test.js × canonical=byte-identical sha256 unique=1·4 树ccFormatTokensOr=function一致)。GUARDS 红线节。下一候选(part b/c 续):token 数字格式器家族(spinner 刀35 + 面板刀36)已彻底收口;回 part b 前端逐项对齐或新探更高价值显示逻辑发散。 - [x] 刀37 粘贴胶囊
[Pasted text #N +M lines]的 M 三处各写text.split('\n').length(= 行数 = CC 值 +1、CRLF 多计残留 \r、裸 CR 当 1 行),新纯叶子cli/pastedRefLines.js::pastedRefLineCountOr(text, legacy, env)移植 CCgetPastedTextRefNumLines为单一真源(对齐 CCsrc/history.ts) —— 背后逻辑:CC 那个 M 不是「行数」而是换行符个数(text.match(/\r\n|\r|\n/g)||[]).length,CC 源注释逐字「line1\nline2\nline3→ +2 lines(不是 3),We preserve that behavior」——胶囊里的 "+M" 是「首行之外额外多少行」的增量记号(无尾随换行 M=行数−1),且\r\n/裸\r/\n各归一计一次。真缺口=三处repl.js:2851(_injectPasteIntoLine)/repl.js:2875(_storePendingPaste)/repl/busyInputClassifiers.js:24(忙队列预览)各split('\n').length→ 每次多行粘贴胶囊多报一行(3 行粘贴显+3应+2),CRLF 粘贴split('\n')既多计又残留\r、老式 Mac 裸\r整段当 1 行。SSOT 纪律=CC 有专用 SSOTgetPastedTextRefNumLines·新纯叶子(零 IO、确定性、*Or(value,legacy,env)镜像 ccFormatTokensOr/ccFormatCostOr 约定)移植之·三处共源。诚实边界=① 真缺口(CC 专用 SSOT·khy 三处各多报一行+CRLF 误计)非 no-op;②repl.js:2875的lineCount(split 行数)刻意保留驱动两处非显示判定:isShort(≤5 行内联 vs 折叠的产品决策)+ 中文提示已折叠粘贴 N 行(语义=总行数非 "+N delta"·改成换行数会把 3 行说成「2 行」=错)——只为标签新增_displayLines走 CC 口径·刻意不收敛 isShort/中文提示(度量的是别的东西·tag 与 hint 数字刻意各异:tag CC delta、hint 总行数);③busyInputClassifiers无 bare 分支·门控开且换行数 0(单行巨贴)→ 照 CCformatPastedTextRef走裸[Pasted text](不显 "+0 lines")·门控关逐字节回退历史无条件+N lines;④ 分支判定保持各 call-site 原条件(repl.jslineCount>1⟺ CCnumLines>0·bare/非-bare 边界恒等→只换显示数字不动分支);⑤ tag/expand 正则匹配\+\d+ lines与数值无关、展开按#id查_pasteArchive·改显示数字不破坏标签剥离/展开;⑥ 门控关→各 call-site 逐字节回退原split('\n').length。门控KHY_PASTED_REF_LINES默认开·关→逐字节回退。验证:pastedRefLines.test.js(node:test)9/9(换行数 "+2 not 3" / CRLF·裸 CR 归一 / 尾随换行计入 / 防呆 null·非串·空 /*Or门控梯 / 门控开关唯一分歧=计数口径 2vs3 / 默认门控)+busyInputClassifiers.test.js(jest 27/27:改默认开断言 CC tally+2/+1·新增门控关 byte-identity 回退+3·全树仅此文件断言粘贴标签)+ 端到端门控开/关探针(5 行体 OFF+5/ON+4·CRLF 3 行体 OFF+3/ON+2)。三守卫过(leaf-contract × pastedRefLines.js 净·agent-rules/model-hardcoding × pastedRefLines.js+busyInputClassifiers.js 净)·bundled 三副本重建(pastedRefLines.js/repl.js/busyInputClassifiers.js/两测试 × canonical=byte-identical sha256 unique=1·4 树pastedRefLineCountOr=function一致)。GUARDS 红线节。下一候选(part b/c 续):粘贴胶囊行数口径已收口;回 part b 前端逐项对齐或新探更高价值显示逻辑发散。 - [x] 刀38 写文件结果摘要
已写入 <file>(… 行,<size>)里的 size 仍直接内联裸${result.bytes}B,是ccFormatFileSize同族漏掉的最后一支孤儿,收敛进既有 SSOT(对齐 CCformatFileSize) —— 背后逻辑:字节数 → 人类可读应走ccFormatFileSize(30 bytes/5KB/1.5MB),与「图片大小展示一律走 ccFormatFileSize」同口径。真缺口=同模块toolResultSummary.js的 image-read 摘要(:206)、webfetch 摘要(:267)都已 route 进ccFormatFileSizeSSOT,唯独写摘要(:360-361)仍打印裸${result.bytes}B——大文件51200B完全不可读、且与同模块另两处不一致(平行于刀35 spinner token 孤儿)。SSOT 纪律=不新建叶子(ccFormatFileSize已是 SSOT,新建=重复违红线),新增模块级_writeSizeDisplay(bytes, env)(门控开→ccFormatFileSize(bytes) || ${bytes}B;门控关→${bytes}B),镜像同文件 image/webfetch 的 try/catch require 模式,把写分支两处${result.bytes}Broute 进它。诚实边界=① 真缺口(同模块另两处早走 SSOT·写摘要是孤儿)非 no-op;② 收敛到既有 SSOT 非新建重复;③ 保留「显示大小」既有行为(只把裸NBhumanize·绝不删 size 段);④ 小文件30B → 30 bytes是 CC 同族口径(与 image/webfetch 一致)非退化·真正收益在大文件(51200B → 50KB);⑤ 门控关 / require 失败 / 空返回 → 逐字节回退${bytes}B。门控KHY_WRITE_SIZE_CCFORMAT默认开·关→逐字节回退。验证:toolResultSummary.test.js(jest 65/65:含 sub-1KB→N bytes/ 大文件 51200→50KB/ 小数 1536→1.5KB/ 门控关 byte-identityNB回退 + 既有 8 处写摘要断言同步更新为N bytes)+ 端到端门控开/关探针(51200 ON50KB/OFF51200B)。守卫过(agent-rules/model-hardcoding × toolResultSummary.js 净;非纯叶子不跑 leaf-contract)·bundled 三副本重建(toolResultSummary.js/测试 × canonical=byte-identical sha256 unique=1·4 树summarizeToolResult=function一致)。无新叶子无新测试模块(SSOTccFormatFileSize已测)。GUARDS 红线节。下一候选(part b/c 续):ccFormatFileSize字节 humanize 家族(image/webfetch/write 三处)已彻底收口;回 part b 前端逐项对齐或新探更高价值显示逻辑发散。 - [x] 刀39 用户可见英文计数串硬编码复数形,
n === 1显「1 matches」/「1 lines」/「1 files」=英文语法错;移植 CCstringUtils.ts::plural为纯叶子ccPlural,三处用户可见 call-site 收敛(对齐 CCplural) —— 背后逻辑:CC 用plural(n, word, pluralWord = word + 's') => n === 1 ? word : pluralWord取代散落的内联word${n===1?'':'s'}习语,统一英文计数串单复数,严格=== 1(string'1'取复数,不强转)。真缺口=Khy 多处用户可见计数串硬编码复数形,n === 1时显「1 matches」「1 lines」「1 files」——handlers/session.js:596(搜索结果头Search Results (N matches))、atMentionInject.js:125(@dir注入 Read 行 sizeInfoN files)、toolDisplay.js:392(经典 REPL✏️ Writing行(N lines)),三处均 reachable(search 仅上文results.length === 0守卫)。SSOT 纪律=全树确认无既有 plural SSOT(非重复),新建纯叶子cli/ccPlural.js(零 IO·零业务 require·plural逐字节移植 +pluralOr(n,word,pluralWord?,env)门控包装:门控开→CC plural;门控关→复数形=各 call-site 历史硬编码形逐字节回退,因本族 legacy 一律复数)。诚实边界=① 真缺口(n===1显「1 matches」是确凿英文语法错)非 no-op;② 移植 CC 真实 SSOT 非编造;③ 无既有 plural SSOT 非重复叶子;④ 刻意排除动词一致串(permissionDialog.js:650「N tools require approval」,n===1应「1 tool requires approval」,名词-only 叶子产「1 tool require approval」仍错·且非 CC 组件移植)+ 诊断命令(verify.js errors/warnings·窄范围)+ 混合类型串(ai.js:4845fp.lines可能为'?');⑤ 门控关 → 复数形逐字节回退。门控KHY_CC_PLURAL默认开。验证:ccPlural.test.js(node:test 8/8:门控梯 /pluraln===1 单数·0,2 复数 / 自定义 pluralWord match→matches / 严格 ===1 字符串取复数 /pluralOr门控开分歧·门控关恒复数 byte-identity / 唯一分歧在 n===1 / 默认门控开)+ 端到端门控开/关探针(ON「1 match」/「1 line」/「1 file」·OFF「1 matches」/「1 lines」/「1 files」)。守卫过(leaf-contract × ccPlural.js·agent-rules/model-hardcoding × ccPlural.js+session.js+atMentionInject.js+toolDisplay.js 全净)·bundled 三副本重建(ccPlural.js/测试 + 三 call-site × canonical=byte-identical sha256 unique=1·4 树pluralOr=function一致)。GUARDS 红线节。下一候选(part b/c 续):英文计数串单复数族(session/写显示/@dir三处名词-only)已收口;permissionDialog 动词一致串诚实 NA;回 part b 前端逐项对齐或新探更高价值显示逻辑发散。 - [x] 刀40 TUI 工具失败详情每行固定
truncate(ln,120),展开(Ctrl+O)态仍裁 120 → 超长错误行尾部永看不到、且无视终端宽;改走既有diffClipWidthSSOT,与姊妹 literal stdout 分支(刀17)同口径(对齐 CC verbose 全展) —— 背后逻辑:工具失败详情行与 diff/literal 行同属「随终端列宽 + Ctrl+O 展开态自适应裁切」家族——展开应diffClipWidth→Infinity(整行交 ink 自然换行、Ctrl+O 真全貌),折叠应裁到终端感知宽(避免裁出宽于 TTY 的行被 ink 二次换行撑破折叠行预算)。真缺口=ToolLines.js错误块detailLines.forEach(... truncate(ln,120))固定 120,与同文件 literal stdout 分支(刀17 已修成diffClipWidth,gutterWidth=4)不一致:① 80 列 TTY 上裁 120 仍宽于终端;② 展开后仍裁 120 → 超长错误行(长路径 ENOENT/长 JSON 校验行/长编译诊断)尾部 Ctrl+O 永看不到,是toolErrorFold「诚实展开」自述原则漏掉的最后一支(literal 姊妹刀17 已修、error 这支当时漏过)。SSOT 纪律=route 进既有diffClipWidthSSOT,不新建叶子(新叶子=重复违红线);复用既有门控KHY_TOOL_ERROR_FOLD(整个多行错误忠实渲染特性同一开关)不新增开关;gutterWidth=3(外层工具块 marginLeft 1 + err 块 marginLeft 2、错误行无⎿前缀);沿用truncate(ln, _errClipW)与 literal 姊妹完全一致。诚实边界=① 真缺口(展开态仍裁 120 → 超长错误行尾部 Ctrl+O 看不到)非 no-op(回归实证:gate ON+expanded 整行可见无…、gate ON+collapsed 终端感知截尾带…,两态可见差异);② route 既有 SSOT 非新建重复;③ 复用既有门控不新增开关;④ 刻意排除行内空白保留——错误行行内空白早由stripInternalControlText(preserveNewlines 分支.map(ln=>ln.replace(/[ \t]+/g,' ').trim()))逐行折叠 + trim(刀18 刻意:保留行间结构、归一行内空白),属上游既定设计,绝不在渲染层「恢复」缩进(那会改刀18 既定形态、违「绝不改 khy 刻意不同代码」红线)=honest-NA,本刀只治宽度/展开一面;⑤ 门控关 → 固定truncate(ln,120)逐字节回退。门控KHY_TOOL_ERROR_FOLD默认开。验证:node scripts/run-ink-tui-tests.js(327/327,含tests/tui/toolLinesErrorFaithful.test.js3/3:gate ON+expanded 整行无…、gate ON+collapsed 终端感知截尾带…、gate OFF+expanded 固定 120 截尾带…)。守卫过(agent-rules/model-hardcoding × ToolLines.js 全净;TUI 组件非纯叶子不跑 leaf-contract)·bundled 三副本重建(ToolLines.js/测试 × canonical=byte-identical sha256 unique=1·三树 require OK)。无新叶子(收敛到既有diffClipWidth)。GUARDS 红线节。下一候选(part b/c 续):工具失败详情行已与 diff/literal 收口到diffClipWidth同 SSOT;回 part b 前端逐项对齐或新探更高价值显示逻辑发散。 - [x] 刀41 spinner token byline 误用
ccFormatTokens(去.0,显8k/45k/1m),与 CCformatNumber(保.0→8.0k/45.0k/1.0m)+ 同文件 legacy 回退 + agentStatLine 自述同族铁律三方背离;换ccFormatNumber(对齐 CCSpinnerAnimationRow.tsx:178) —— 背后逻辑:CC spinner bylineconst tokenCount = formatNumber(totalTokens)——formatNumber对 n≥1000 钉minimumFractionDigits:1保留尾随.0(8000→8.0k、45000→45.0k、1000000→1.0m);formatTokens=formatNumber(n).replace('.0','')去.0,二者非同一 helper。真缺口=cli/spinner.jsfmtTokens闭包误接ccFormatTokens,与三方背离:① CCformatNumber;② 同文件 legacy 回退(n/1000).toFixed(1)k(本就保.0,即迁移到ccFormatTokens才引入背离);③agentStatLine.js:8-15自述同族铁律「AgentProgressLine/TeammateSpinnerLine/CoordinatorAgentStatus/Spinner全走formatNumber(注意是 formatNumber 而非 formatTokens,保留尾随.0)」——spinner 是该族唯一被接错的一支(且 spinner 自身注释声称对齐formatNumber却给formatTokens示例45000→45k,注释意图与代码/示例自相矛盾)。SSOT 纪律=route 进既有ccFormatNumberSSOT,不新建叶子;复用既有门控KHY_CC_FORMAT(同族门控)不新增开关。诚实边界=① 真缺口(整千/整百万值丢.0,三方背离)非 no-op(回归实证:gate ON 8000→8.0k tokens不再8k tokens、1000000→1.0m);② route 既有 SSOT 非新建重复;③ 复用既有门控不新增开关;④ 刻意排除hudRenderer/turnStats的fmtTokens(走ccFormatTokens=khy 自有面、与 CCStatusLine/GoalPill用formatTokens同口径,各自正确)+↑ input / ↓ output双箭头(CC 仅↓ total,khy 刻意多给信息非缺陷)+ effort 低/中档裸词· medium(khy 自有中文动词轮换体系,无 CC 可复制确定性结构,不强塞);⑤ 门控关 / 非有限 → legacy(n/1000).toFixed(1)k逐字节回退(亦保.0)。门控KHY_CC_FORMAT默认开。验证:node --test tests/cli/spinnerTokenByline.test.js(4/4:gate ON 8000→8.0k·1000000→1.0m·12345→12.3k(非整千值与 formatTokens 同·无回归)·gate OFF legacy8.0k;探针拨_startTime至 31s 前越过 30s reveal-gate、stubprocess.stdout.write捕获帧)+ 相关回归(spinnerMeta/turnStats/hudTokensSsot node:test 26/26·ab_test_interactivity+ab_interactivity_v2 110/111,唯一 fail=GAP-2_formatToolResult截断警告=预存与本刀无关)。守卫过(agent-rules/model-hardcoding × spinner.js 全净;非纯叶子不跑 leaf-contract)·bundled 三副本重建(spinner.js/测试 × canonical=byte-identical sha256 unique=1·三树DynamicSpinner=function)。无新叶子(收敛到既有ccFormatNumber)。GUARDS 红线节。下一候选(part b/c 续):spinner/agent token-byline 家族已彻底收口到formatNumber/formatTokens正确分工;回 part b 前端逐项对齐或新探更高价值显示逻辑发散。 - 下一候选(part b/c 续):mailto + URL 自我重复刀11 已收口。markdown 渲染域既有缺口已穷尽核实(blockquote/下划线/表格换行/有序列表/紧致/链接展示);agent 树域续了统计行(刀12)+ liveness 占位(刀13)+ 时长 elapsed 控制器路径走时长 SSOT(刀20)。spinner reveal-gate 已两前端收口(刀14)。时长格式器家族已收口最后一支孤儿:spinner/step/task 面板
_formatElapsed走 ccFormatDuration(刀21)。diff/输出域已收口六刀:diff 内容列裁切宽度(刀15)+ 词级 changeRatio 度量口径(刀16)+ literal stdout 裁切宽度复用同 SSOT(刀17)+ 工具错误详情多行折叠(刀18·补刀17 漏过的错误行)+ diff/预览溢出标记文本+复数守卫收敛previewOverflowMarker,N=1 不再「+1 lines」(刀31·两引擎共用·补刀27 扫描漏看的 overflow marker)。task-panel 域已续隐藏项摘要按状态分解(刀19)+ 已完成行 strikethrough 对齐 CC(刀23·整行单 Text 划线绕开拆节点风险,解了刀19 的暂缓)+ 头行按状态计数对齐 CCTaskListV2isStandalone 头行(刀28·新叶子taskPanelHeader·消费countTaskLinesByStatus图标逆映射 SSOT·计数覆盖全量·error 诚实单列)+ 快照行 blocked-by 后缀只列仍未完成依赖、已完成剔除全完成则消失(刀29·新叶子taskBlockedBySuffix·对齐 CCopenBlockers·保留 khy 悬空依赖告警的刻意分歧·快照层显示对齐canStart判定不再自相矛盾)+ 超高截断从盲目尾切改按状态生存优先级保活(刀30·新叶子taskPanelLines.selectTaskLinesByPriority·对齐 CC 截断优先级prioritized.slice·进行中/错误 > 待办 > 已完成·同档守 khy 尾锚定·高度中性·绝不臆造完成时间戳照搬 CC 的 recentCompleted 30s 分桶)。工具头 arg-summary 长度上限域:Bashcommand头对齐 CCMAX_COMMAND_DISPLAY_CHARS=160(刀22·新叶子toolHeaderCap)。TUI 补全菜单过滤域:斜杠菜单走rankSlashCommands子串/分段/描述匹配(刀24·新叶子slashMenuFilter·消费既有 SSOT 与经典 REPL 收敛)。经典 ANSI 编辑 diff 域:renderStructuredDiff改走真分 hunk 消费既有computeStructuredDiffHunksSSOT、与默认 TUI 收敛(刀25·绝不再 churn 重绘未改行/虚报 ±计数)。工具失败串受众拆分域:给「人」的入参校验失败串折成单行Invalid tool parameters、完整分组只给「模型」+Ctrl+O(刀26·新叶子ccUserFacingToolError·消费产串方自识别签名ccValidationError.isValidationErrorMessage)。工具头「读取范围」算术域:Read 头行路径后追加offset/limit→第 X-Y 行/从第 X 行起(刀27·新叶子readRangeSuffix·两处头渲染收敛·绝不臆造 CC 的pages分支因 Khy read 无 pages 参数)。下一候选(part b/c 续):toolDisplay.js:961的+N more tool uses(经典 REPL agent 工具用量计数 N=1 复数·刀31 刻意留作后续)·回到 part b 前端逐项对齐或新探一处更高价值的显示逻辑发散。已穷尽核实归档:ccFormat/format.ts 族(刀3-7)·blockquote 正文(刀8)·下划线强调(刀9)·diff 整宽色条经典路径(刀10)·markdown 链接展示 mailto/去重(刀11)·agent 统计行三分段单复数/紧凑记数/h 进位(刀12)·agent liveness 活动占位(刀13)·spinner 计时+token 30s reveal-gate ink TUI 收口(刀14)·diff 内容列裁切宽度终端感知+展开换行(刀15)·diff 词级 changeRatio 字符长度口径(刀16)·literal stdout 裁切宽度复用 diffClipWidth(刀17)·工具错误详情多行折叠 10+页脚+Ctrl+O 显全(刀18)·任务清单隐藏项摘要按状态分解(刀19)·agent 树时长 elapsed 控制器路径走agentDurationLabelOrSSOT(刀20)·spinner/step/task 面板秒级时长_formatElapsed走ccFormatDurationSSOT(刀21·时长家族最后一支孤儿)·工具头 Bashcommand长度上限对齐 CCMAX_COMMAND_DISPLAY_CHARS=160(刀22·新叶子toolHeaderCap)·常驻任务面板已完成(✓)行 strikethrough 对齐 CCTaskListV2.strikethrough={isCompleted}(刀23·新叶子taskLineStyle)·TUI 斜杠菜单过滤/排序走rankSlashCommands子串/分段/描述匹配(刀24·新叶子slashMenuFilter·与经典 REPL 收敛)·经典 ANSI 文件编辑 diffrenderStructuredDiff走真分 hunk 消费computeStructuredDiffHunksSSOT、块间⋯ N unchanged lines分隔、└摘要喂真 ±计数(刀25·与默认 TUI 收敛·非措辞/计数格式/折叠)·工具失败串受众拆分:给「人」的入参校验失败串折单行Invalid tool parameters、完整分组只给「模型」+Ctrl+O 展开(刀26·新叶子ccUserFacingToolError消费产串方自识别签名ccValidationError.isValidationErrorMessage·render-only 模型侧不动·与刀「给模型分组措辞」互补正交)·Read 头行「读取范围」后缀:路径后追加offset/limit→第 X-Y 行/从第 X 行起,收敛toolDisplay.js+ToolLines.js两处头渲染(刀27·新叶子readRangeSuffix移植 CCstartLine + limit - 1算术·绝不移植pages分支因 Khy read 无该参数·offset/limit 无条件展示因 Khy 无 verbose 概念)·diff/预览溢出折叠标记文本+复数守卫收敛previewOverflowMarker,两引擎(TUIToolLines+经典 REPLtoolOutputRender)共用,N=1 守单数+1 line/1 more row(刀31·补刀27 扫描只核 gap 分隔行、漏看同函数 overflow marker 的盲点·门控开 N>1 字节一致·toolDisplay.js:961的+N more tool uses刻意留作后续不顺手扩进)·常驻任务面板头行按状态计数:静态任务清单升级为任务清单(共 N 项:X 完成、Y 进行中、Z 待办[、W 错误]),对齐 CCTaskListV2isStandalone 头行{N} tasks ({done} done, [{ip} in progress, ]{open} open)(刀28·新叶子taskPanelHeader消费taskPanelLines.countTaskLinesByStatus图标逆映射 SSOT·计数覆盖全量可见+隐藏·进行中/error 仅 >0·error 诚实单列因 khy 有显式 ✗ 状态·任一诚实闸门触发回退静态标题);honest-NA=有序列表深度标号(无 AST 不可忠实)·标题 cyan+图标(khy 刻意主题)·链接 OSC-8(khy 刻意用显式text (dim url)·不依赖 OSC-8 能力)·diff 整宽色条 TUI 路径(ink@6.8.0 output.js trimEnd 裁尾随背景空格·CC 用 @anthropic/ink 分叉无此 trim·改 ink=架构变更)·~~text~~删除线(khy 已用~~(?!~)双波浪线·~100单波浪线「约等于」本就不误删=已正确·CC 整体禁 del 是杀单~的连带·照搬反而删掉正确特性=NA)·agent tree glyph├─/└─(纯字形美学·logic 极薄)·spinner verbose/teammates 旁路(khy 此路径无此概念·诚实只 30s 钟生效不伪造旁路)·diff 展开态长词级配对行的彩色段重排(ink 自动换行多段<Text>·本叶子只产宽度数值)·diff 词级 changeRatio 阈值常量/分词规则/LCS(刀16 只改度量口径不动这些)·TUI 权限框选项过滤/@-mention 文件补全/thinking 块折叠(刀24 核实=NA:权限框是固定 allow/deny 枚举无搜索·@-mentioncomputeFile已startsWith路径前缀与 CC 文件补全口径一致无发散·Khy 无独立 thinking 块渲染面造则 net-new)·工具结果计数单复数=== 1 ? 'line':'lines'/ GrepcountLabel(刀27 扫候选核实=NA:Khy 人面工具结果走透明输出·不渲染 CC「Read N lines」计数摘要行·真存在的计数面要么是模型面 metadata 单复数无关,要么已正确守卫——但刀31 补正:该 NA 当时漏看ToolLines.js/toolOutputRender.js的 overflow marker±N lines/N more rows未守复数,已由刀31 收口)·Grep/Glob 结果摘要Found N matches across M files(刀27 核实=NA:KhytoolResultSummary.js已渲染中文「找到 N 个匹配,跨 M 个文件」·中文无单复数)·Read 头行pages分支(刀27 核实=NA 且诚实红线:KhyreadFile.jsinputSchema 无pages参数·凭空造· pages N= 臆造不携带的数据)。·diff 整宽色条 TUI 路径(ink@6.8.0 output.js trimEnd 裁尾随背景空格·CC 用 @anthropic/ink 分叉无此 trim·改 ink=架构变更)·~~text~~删除线(khy 已用~~(?!~)双波浪线·~100单波浪线「约等于」本就不误删=已正确·CC 整体禁 del 是杀单~的连带·照搬反而删掉正确特性=NA)·agent tree glyph├─/└─(纯字形美学·logic 极薄)·spinner verbose/teammates 旁路(khy 此路径无此概念·诚实只 30s 钟生效不伪造旁路)·diff 展开态长词级配对行的彩色段重排(ink 自动换行多段<Text>·本叶子只产宽度数值)·diff 词级 changeRatio 阈值常量/分词规则/LCS(刀16 只改度量口径不动这些)·TUI 权限框选项过滤/@-mention 文件补全/thinking 块折叠(刀24 核实=NA:权限框是固定 allow/deny 枚举无搜索·@-mentioncomputeFile已startsWith路径前缀与 CC 文件补全口径一致无发散·Khy 无独立 thinking 块渲染面造则 net-new)·bash 命令头 2 行+160 字两段截断(刀17 核实=NA:khy_truncateDisplayWidth刻意replace(/\s+/g,' ')把多行命令折成单行 space-join 头·另在 8 行 box 预览显全貌·CC 的 2 行规则是其多行 Text 头的不同但各自正确的选择·照搬不适配 khy 单行(...)头且删不掉任何坏东西=khy 刻意正确不改)·literal stdout 折叠 head-only(CC head-only vs khyfoldOutputhead+tail·各自正确·照搬会删 khy 正确尾部展示=NA)·错误详情折叠 head-only(刀18 核实=CC 错误折叠本就是 head-only·与 literal 的 head+tail 不同但错误场景 head-only 即 CC 口径·刀18 照 CC 取 head 10·诚实)·任务清单优先级截断排序/strikethrough/bold/blocked-by 行内样式(刀19 核实:khy 尾锚定截断与 CC recentCompleted→inProgress→pending→olderCompleted 优先级截断各自正确·strikethrough completed 需拆分图标/正文 Text 节点风险大·本刀只治隐藏项摘要分组口径不动行内样式=暂缓/NA→刀23 已解:改用整行单 Text 划线(不拆节点)绕开该风险,门控开 ✓ 行strikethrough:true,整行含图标被划=刻意简化非缺陷)·agentelapsed改 number 类型(刀20 核实=NA:agent-array 形状全部消费者formatStats字符串分支/toolDisplay:639/panels:661都按 string verbatim push·panelsdocstring 明示@param {string}·改 number 会断这些·agentRenderer:140Math.max(...map(a=>a.elapsed||0))对字符串本就 NaN=预存非 TTY summary 独立 bug·本刀保 string 类型不引入也不修该预存 bug)·HUD{hideTrailingZeros}口径(刀21 核实=NA:HUD 刻意省整分尾零60s→"1m"·是与 spinner PLAIN 链并存的另一约定·非缺口·照搬会改 HUD 正确行为=不动)·工具头命令 2 行多行头MAX_COMMAND_DISPLAY_LINES=2(刀22 核实=NA:KhysummarizeArgs刻意replace(/\s+/g,' ')单行折叠·刀17 已裁定·只对齐字符上限 160 不引入多行头)·grep pattern 头降到 CC 的 50(刀22 核实=NA:Khy 60>CC 50·显得更多非更差·降级=删正确行为)·经典 REPLtoolDisplay.js命令头(刀22 核实=NA:刻意富设计=命令模式识别「git log …」+8 行 box 显全貌·与 TUI 单行头各自正确·刀17 已 NA·本刀仅治 TUI 这一支非两 call-site 收敛)·markdown 表格表头按列对齐(刀25 候选 B 核实=NA:markdownRenderer.js:955表头恒左对齐是带注释的刻意选择「Header row is always left-aligned for readability」·Khy box-drawn 表格本就比 CC plain| a | b |富·照搬 CC 表头右对齐=删既有刻意设计违诚实红线)·经典renderDiff(unified-diff)行号 gutter(刀25 候选 C 核实=NA:与 diff-gutter 家族近重复·同属KHY_FULL_TUI=0经典回退面·价值低不追)·给「人」折叠工具校验失败串的经典 readline REPL 路径(刀26 核实=NA:repl.js:7636那支 isErr 已.slice(0,120)把多行折成单行截断=不同的既有行为·且其分支无_expandableOutputs注册→若折成Invalid tool parameters将无 Ctrl+O 恢复=丢信息不诚实·故只治默认 TUI 这一支·经典 REPL honest-NA)·CCError:前缀归一(刀26 核实=NA:改动面遍及所有工具错误·Khy 错误本地化措辞各有设计=各自正确·非清晰缺口·照搬属措辞层变更不做)·consecutive read/search 计数折叠(CC 跨消息分类计数行·更大架构特性且渲染措辞英文语言相关·非单叶子刀·暂缓)·任务面板头行的 standalone vs 非-standalone 渲染分叉(刀28 核实=NA:CC 仅isStandalone显头行计数·非-standalone(嵌在别处)不显·KhyTaskListPanel本就是常驻独立面板=恒等价 standalone·无第二渲染上下文可分叉·照搬isStandalone条件=net-new 无意义分支)·CCinProgressCount = length - completed - pending的「其余皆 in-progress」归并(刀28 核实=刻意不照搬:CC 只有 completed/pending/in_progress 三态故把其余并进 in-progress 是其状态空间所限,Khy 有显式 error(✗)状态,把 error 并进「进行中」会错报=违诚实红线,故 Khy 按真实图标分别计数并单列 error)。·标题 cyan+图标(khy 刻意主题)·链接 OSC-8(khy 刻意用显式text (dim url)·不依赖 OSC-8 能力)·diff 整宽色条 TUI 路径(ink@6.8.0 output.js trimEnd 裁尾随背景空格·CC 用 @anthropic/ink 分叉无此 trim·改 ink=架构变更)·~~text~~删除线(khy 已用~~(?!~)双波浪线·~100单波浪线「约等于」本就不误删=已正确·CC 整体禁 del 是杀单~的连带·照搬反而删掉正确特性=NA)·agent tree glyph├─/└─(纯字形美学·logic 极薄)·spinner verbose/teammates 旁路(khy 此路径无此概念·诚实只 30s 钟生效不伪造旁路)·diff 展开态长词级配对行的彩色段重排(ink 自动换行多段<Text>·本叶子只产宽度数值)·diff 词级 changeRatio 阈值常量/分词规则/LCS(刀16 只改度量口径不动这些)·TUI 权限框选项过滤/@-mention 文件补全/thinking 块折叠(刀24 核实=NA:权限框是固定 allow/deny 枚举无搜索·@-mentioncomputeFile已startsWith路径前缀与 CC 文件补全口径一致无发散·Khy 无独立 thinking 块渲染面造则 net-new)·bash 命令头 2 行+160 字两段截断(刀17 核实=NA:khy_truncateDisplayWidth刻意replace(/\s+/g,' ')把多行命令折成单行 space-join 头·另在 8 行 box 预览显全貌·CC 的 2 行规则是其多行 Text 头的不同但各自正确的选择·照搬不适配 khy 单行(...)头且删不掉任何坏东西=khy 刻意正确不改)·literal stdout 折叠 head-only(CC head-only vs khyfoldOutputhead+tail·各自正确·照搬会删 khy 正确尾部展示=NA)·错误详情折叠 head-only(刀18 核实=CC 错误折叠本就是 head-only·与 literal 的 head+tail 不同但错误场景 head-only 即 CC 口径·刀18 照 CC 取 head 10·诚实)·任务清单优先级截断排序/strikethrough/bold/blocked-by 行内样式(刀19 核实:khy 尾锚定截断与 CC recentCompleted→inProgress→pending→olderCompleted 优先级截断各自正确·strikethrough completed 需拆分图标/正文 Text 节点风险大·本刀只治隐藏项摘要分组口径不动行内样式=暂缓/NA→刀23 已解:改用整行单 Text 划线(不拆节点)绕开该风险,门控开 ✓ 行strikethrough:true,整行含图标被划=刻意简化非缺陷)·agentelapsed改 number 类型(刀20 核实=NA:agent-array 形状全部消费者formatStats字符串分支/toolDisplay:639/panels:661都按 string verbatim push·panelsdocstring 明示@param {string}·改 number 会断这些·agentRenderer:140Math.max(...map(a=>a.elapsed||0))对字符串本就 NaN=预存非 TTY summary 独立 bug·本刀保 string 类型不引入也不修该预存 bug)·HUD{hideTrailingZeros}口径(刀21 核实=NA:HUD 刻意省整分尾零60s→"1m"·是与 spinner PLAIN 链并存的另一约定·非缺口·照搬会改 HUD 正确行为=不动)·工具头命令 2 行多行头MAX_COMMAND_DISPLAY_LINES=2(刀22 核实=NA:KhysummarizeArgs刻意replace(/\s+/g,' ')单行折叠·刀17 已裁定·只对齐字符上限 160 不引入多行头)·grep pattern 头降到 CC 的 50(刀22 核实=NA:Khy 60>CC 50·显得更多非更差·降级=删正确行为)·经典 REPLtoolDisplay.js命令头(刀22 核实=NA:刻意富设计=命令模式识别「git log …」+8 行 box 显全貌·与 TUI 单行头各自正确·刀17 已 NA·本刀仅治 TUI 这一支非两 call-site 收敛)·markdown 表格表头按列对齐(刀25 候选 B 核实=NA:markdownRenderer.js:955表头恒左对齐是带注释的刻意选择「Header row is always left-aligned for readability」·Khy box-drawn 表格本就比 CC plain| a | b |富·照搬 CC 表头右对齐=删既有刻意设计违诚实红线)·经典renderDiff(unified-diff)行号 gutter(刀25 候选 C 核实=NA:与 diff-gutter 家族近重复·同属KHY_FULL_TUI=0经典回退面·价值低不追)·给「人」折叠工具校验失败串的经典 readline REPL 路径(刀26 核实=NA:repl.js:7636那支 isErr 已.slice(0,120)把多行折成单行截断=不同的既有行为·且其分支无_expandableOutputs注册→若折成Invalid tool parameters将无 Ctrl+O 恢复=丢信息不诚实·故只治默认 TUI 这一支·经典 REPL honest-NA)·CCError:前缀归一(刀26 核实=NA:改动面遍及所有工具错误·Khy 错误本地化措辞各有设计=各自正确·非清晰缺口·照搬属措辞层变更不做)·consecutive read/search 计数折叠(CC 跨消息分类计数行·更大架构特性且渲染措辞英文语言相关·非单叶子刀·暂缓)·工具结果计数单复数=== 1 ? 'line':'lines'/'page':'pages'/ GrepcountLabel(刀27 扫候选核实=NA:Khy 人面工具结果走透明输出——直显真实行/结构化 diff,不渲染 CC 那种「Read N lines」计数摘要行;真存在的计数面要么是模型面 metadata${n} total lines(给模型·CC 亦同·单复数无关),要么已正确守卫单复数line${n!==1?'s':''}(diffRenderer.js:388/ToolLines.js:209)·无可达显示发散,造计数面=臆造 Khy 刻意不携带的摘要面)·Grep/Glob 结果摘要Found N matches across M files(刀27 扫候选核实=NA:KhytoolResultSummary.js:148-174已 mode-aware 且渲染中文「找到 N 个匹配,跨 M 个文件」·中文无单复数·CC 的countLabel.slice(0,-1)单数化对中文不适用=各自正确本地化)·File-writeWrote N lines/+N linesoverflow 单复数(刀27 扫候选核实=NA:KhytoolResultSummary.js:361中文「已写入 …(N 行)」·中文无单复数)·扩展思考块折叠/timestamp/cost byline/relative-time/file-size/number 格式(刀27 扫候选核实=NA:thinkingDuration.js/ccFormat.js(ccFormatFileSize/ccFormatNumber/ccFormatTokens/ccFormatDuration/ccRelativeAgeParts/ccBriefTimestamp/ccFormatCost)已忠实移植 CCformat.ts全族·CC Message.tsx 本就无 cost byline·均已覆盖)·Read 头行pages分支(刀27 核实=NA 且诚实红线:KhyreadFile.jsinputSchema 无pages参数·凭空造· pages N= 臆造不携带的数据·刀27 只移植 offset/limit 行区间算术绝不碰 pages)。 - 本轮 honest-NA 核实归档(防后续重复追):② plural
n===1vs khy>1——核实 4 个>1?'s':''call-site(FileEditTool:272/282、MultiEditTool:243、editFile:152)全部位于count===0→error守卫下游(editFile:117、FileEditTool同模式、MultiEditTool:143非空数组守卫),count 恒>=1→>1与!==1逐字节同输出(仅0处发散而0永不到达该串)=非真 bug,不改(守诚实红线绝不造「修复」)。③ timeFormatformatTimeAgo/formatRelativeTimestamp的Math.round向上虚报(90s→"2m")——核实零消费者(仅formatDurationPrecise被diagnosticEvents用),属死导出无任何显示面→对齐它不改变任何用户可见显示=NA。④formatResetTime速率限制重置展示——khy 速率限制全为内部 cooldown/backoff(apiKeyPool/circuitBreaker),无用户可见「resets at」展示面→对齐=net-new 特性非对齐=NA。
- [x] 刀1「% until auto-compact」上下文警告状态机 —— 对齐 CC
承 [[project_cc_alignment_missing_commands_tools]] · 接线契约见 [DESIGN-ARCH-060]。